Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>: A = 1;
* N_7: print(A);
*
* At N_3, reads of A in {N_4, N_5} are said to be upward exposed.
*/
static final class ReachingUses implements LatticeElement {
final Multimap<Var, Node> mayUseMap;
public ReachingUses() {
mayUseMap = HashMultimap.create();
}
/**
* Copy constructor.
*
* @param other The constructed object is a replicated copy of this element.
*/
public ReachingUses(ReachingUses other) {
mayUseMap = HashMultimap.create(other.mayUseMap);
}
@Override
public boolean equals(Object other) {
return (other instanceof ReachingUses) &&
((ReachingUses) other).mayUseMap.equals(this.mayUseMap);
}
@Override
public int hashCode() {
return mayUseMap.hashCode();
}
}
/**
* The join is a simple union because of the "may be" nature of the analysis.
*
* Consider: A = 1; if (x) { A = 2 }; alert(A);
*
* The read of A "may be" exposed to A = 1 in the beginning.
*/
private static class ReachingUsesJoinOp implements JoinOp<ReachingUses> {
@Override
public ReachingUses apply(List<ReachingUses> from) {
ReachingUses result = new ReachingUses();
for (ReachingUses uses : from) {
result.mayUseMap.putAll(uses.mayUseMap);
}
return result;
}
}
@Override
boolean isForward() {
return false;
}
@Override
case Token.DO:
case Token.IF:
computeMayUse(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
return;
case Token.FOR:
if (!NodeUtil.isForIn(n)) {
computeMayUse(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
} else {
// for(x in y) {...}
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
if (lhs.isVar()) {
lhs = lhs.getLastChild(); // for(var x in y) {...}
}
if (lhs.isName() && !conditional) {
removeFromUseIfLocal(lhs.getString(), output);
}
computeMayUse(rhs, cfgNode, output, conditional);
}
return;
case Token.AND:
case Token.OR:
computeMayUse(n.getLastChild(), cfgNode, output, true);
computeMayUse(n.getFirstChild(), cfgNode, output, conditional);
return;
case Token.HOOK:
computeMayUse(n.getLastChild(), cfgNode, output, true);
computeMayUse(n.getFirstChild().getNext(), cfgNode, output, true);
computeMayUse(n.getFirstChild(), cfgNode, output, conditional);
return;
case Token.VAR:
Node varName = n.getFirstChild();
Preconditions.checkState(n.hasChildren(), "AST should be normalized");
if (varName.hasChildren()) {
computeMayUse(varName.getFirstChild(), cfgNode, output, conditional);
if (!conditional) {
removeFromUseIfLocal(varName.getString(), output);
}
}
return;
default:
if
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> (NodeUtil.isAssignmentOp(n) && n.getFirstChild().isName()) {
Node name = n.getFirstChild();
if (!conditional) {
removeFromUseIfLocal(name.getString(), output);
}
// In case of a += "Hello". There is a read of a.
if (!n.isAssign()) {
addToUseIfLocal(name.getString(), cfgNode, output);
}
computeMayUse(name.getNext(), cfgNode, output, conditional);
} else {
/*
* We want to traverse in reverse order because we want the LAST
* definition in the sub-tree....
* But we have no better way to traverse in reverse other :'(
*/
for (Node c = n.getLastChild(); c != null; c = n.getChildBefore(c)) {
computeMayUse(c, cfgNode, output, conditional);
}
}
}
}
/**
* Sets the variable for the given name to the node value in the upward
* exposed lattice. Do nothing if the variable name is one of the escaped
* variable.
*/
private void addToUseIfLocal(String name, Node node, ReachingUses use) {
Var var = jsScope.getVar(name);
if (var == null || var.scope != jsScope) {
return;
}
if (!escaped.contains(var)) {
use.mayUseMap.put(var, node);
}
}
/**
* Removes the variable for the given name from the node value in the upward
* exposed lattice. Do nothing if the variable name is one of the escaped
* variable.
*/
private void removeFromUseIfLocal(String name, ReachingUses use) {
Var var = jsScope.getVar(name);
if (var == null || var.scope != jsScope) {
return;
}
if (!escaped.contains(var)) {
use.mayUseMap.removeAll(var);
}
}
/**
* Gets a list of nodes that may be using the value assigned to {@code name}
* in {@code defNode}. {@code defNode} must be one of the control flow graph
* nodes.
*
* @param name name of the variable. It can only be names of local variable
* that are not function parameters, escaped variables or variables
* declared in catch.
* @param defNode The list of upward exposed use for the variable.
*/
Collection<Node> getUses(String name, Node defNode) {
GraphNode<Node, Branch> n = getCfg().getNode(defNode);
Preconditions.checkNotNull(n);
FlowState<ReachingUses> state = n.getAnnotation();
return state.getOut().mayUseMap.get(jsScope.getVar(name));
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2012 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.javascript.jscomp.DefaultPassConfig.HotSwapPassFactory;
import com.google.javascript.jscomp.GlobalVarReferenceMap.GlobalVarRefCleanupPass;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import java.util.List;
/**
* Provides passes that should be run before hot-swap/incremental builds.
*
* @author tylerg@google.com (Tyler Goodwin)
*/
class CleanupPasses extends PassConfig {
private State state;
public CleanupPasses(CompilerOptions options) {
super(options);
}
@Override
protected List<PassFactory> getChecks() {
List<PassFactory> checks = Lists.newArrayList();
checks.add(fieldCleanupPassFactory);
checks.add(scopeCleanupPassFactory);
checks.add(globalVarRefCleanupPassFactory);
return checks;
}
@Override
protected State getIntermediateState() {
return state;
}
@Override
protected List<PassFactory> getOptimizations() {
return ImmutableList.of();
}
@Override
protected void setIntermediateState(State state) {
this.state = state;
}
final PassFactory fieldCleanupPassFactory =
new HotSwapPassFactory("FieldCleaupPassFactory", false) {
@Override
protected HotSwapCompilerPass createInternal(
AbstractCompiler compiler) {
return new FieldCleanupPass(compiler);
}
};
final PassFactory scopeCleanupPassFactory =
new HotSwapPassFactory("ScopeCleanupPassFactory", false) {
@Override
protected HotSwapCompilerPass createInternal(
AbstractCompiler compiler) {
return new MemoizedScopeCleanupPass(compiler);
}
};
final PassFactory globalVarRefCleanupPassFactory =
new HotSwapPassFactory("GlobalVarRefCleanupPassFactory", false) {
@Override
protected HotSwapCompilerPass createInternal(
AbstractCompiler compiler) {
return new GlobalVarRefCleanupPass(compiler);
}
};
/**
* A CleanupPass implementation that will remove stored scopes from the
* MemoizedScopeCreator of the compiler instance for a the hot swapped script.
* <p>
* This pass will also clear out Source Nodes of Function Types declared on
* Vars tracked by MemoizedScopeCreator
*/
static class MemoizedScopeCleanupPass implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
public MemoizedScopeCleanupPass(AbstractCompiler compiler) {
this.compiler
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>ClassReference(propertyName);
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
return nextConvention.extractClassNameIfProvide(node, parent);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
return nextConvention.extractClassNameIfRequire(node, parent);
}
@Override
public String getExportPropertyFunction() {
return nextConvention.getExportPropertyFunction();
}
@Override
public String getExportSymbolFunction() {
return nextConvention.getExportSymbolFunction();
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
return nextConvention.identifyTypeDeclarationCall(n);
}
@Override
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type) {
nextConvention.applySubclassRelationship(
parentCtor, childCtor, type);
}
@Override
public String getAbstractMethodName() {
return nextConvention.getAbstractMethodName();
}
@Override
public String getSingletonGetterClassName(Node callNode) {
return nextConvention.getSingletonGetterClassName(callNode);
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
nextConvention.applySingletonGetter(
functionType, getterType, objectType);
}
@Override
public boolean isInlinableFunction(Node n) {
return nextConvention.isInlinableFunction(n);
}
@Override
public DelegateRelationship getDelegateRelationship(Node callNode) {
return nextConvention.getDelegateRelationship(callNode);
}
@Override
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate) {
nextConvention.applyDelegateRelationship(
delegateSuperclass, delegateBase, delegator,
delegateProxy, findDelegate);
}
@Override
public String getDelegateSuperclassName() {
return nextConvention.getDelegateSuperclassName();
}
@Override
public void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions) {
nextConvention.checkForCallingConventionDefiningCalls(
n, delegateCallingConventions);
}
@Override
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions) {
nextConvention.defineDelegateProxyPrototypeProperties(
registry, scope, delegateProxyPrototypes, delegateCallingConventions);
}
@Override
public String getGlobalObject() {
return nextConvention.getGlobalObject();
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return nextConvention.getAssertionFunctions();
}
@Override
public Bind describeFunctionBind(Node n) {
return describeFunctionBind(n, false);
}
@Override
public Bind describeFunctionBind(Node n, boolean useTypeInfo) {
return nextConvention.describeFunctionBind(n, useTypeInfo);
}
@Override
public boolean isPropertyTestFunction(Node call) {
return nextConvention.isPropertyTestFunction(call);
}
@Override
public boolean isPrototypeAlias(Node getProp) {
return false;
}
@Override
public ObjectLiteralCast getObjectLiteralCast(Node callNode) {
return nextConvention.getObjectLiteralCast(callNode);
}
@Override
public Collection<String> getIndirectly
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>DeclaredProperties() {
return nextConvention.getIndirectlyDeclaredProperties();
}
}
/**
* The default coding convention.
* Should be at the bottom of all proxy chains.
*/
private static class DefaultCodingConvention implements CodingConvention {
private static final long serialVersionUID = 1L;
@Override
public boolean isConstant(String variableName) {
return false;
}
@Override
public boolean isConstantKey(String variableName) {
return false;
}
@Override
public boolean isValidEnumKey(String key) {
return key != null && key.length() > 0;
}
@Override
public boolean isOptionalParameter(Node parameter) {
// be as lax as possible, but this must be mutually exclusive from
// var_args parameters.
return !isVarArgsParameter(parameter);
}
@Override
public boolean isVarArgsParameter(Node parameter) {
// be as lax as possible
return parameter.getParent().getLastChild() == parameter;
}
@Override
public boolean isExported(String name, boolean local) {
return local && name.startsWith("$super");
}
@Override
public boolean isExported(String name) {
return isExported(name, false) || isExported(name, true);
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return false;
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String getExportPropertyFunction() {
return null;
}
@Override
public String getExportSymbolFunction() {
return null;
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
return null;
}
@Override
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type) {
// do nothing
}
@Override
public String getAbstractMethodName() {
return null;
}
@Override
public String getSingletonGetterClassName(Node callNode) {
return null;
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
// do nothing.
}
@Override
public boolean isInlinableFunction(Node n) {
Preconditions.checkState(n.isFunction());
return true;
}
@Override
public DelegateRelationship getDelegateRelationship(Node callNode) {
return null;
}
@Override
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate) {
// do nothing.
}
@Override
public String getDelegateSuperclassName() {
return null;
}
@Override
public void checkForCallingConventionDefiningCalls(Node n,
Map<String, String> delegateCallingConventions) {
// do nothing.
}
@Override
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.debugging.sourcemap.FilePosition;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.nio.charset.Charset;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
/**
* CodePrinter prints out JS code in either pretty format or compact format.
*
* @see CodeGenerator
*/
class CodePrinter {
// The number of characters after which we insert a line break in the code
static final int DEFAULT_LINE_LENGTH_THRESHOLD = 500;
// There are two separate CodeConsumers, one for pretty-printing and
// another for compact printing.
// There are two implementations because the CompactCodePrinter
// potentially has a very different implementation to the pretty
// version.
private abstract static class MappedCodePrinter extends CodeConsumer {
final private Deque<Mapping> mappings;
final private List<Mapping> allMappings;
final private boolean createSrcMap;
final private SourceMap.DetailLevel sourceMapDetailLevel;
protected final StringBuilder code = new StringBuilder(1024);
protected final int lineLengthThreshold;
protected int lineLength = 0;
protected int lineIndex = 0;
MappedCodePrinter(
int lineLengthThreshold,
boolean createSrcMap,
SourceMap.DetailLevel sourceMapDetailLevel) {
Preconditions.checkState(sourceMapDetailLevel != null);
this.lineLengthThreshold = lineLengthThreshold <= 0 ? Integer.MAX_VALUE :
lineLengthThreshold;
this.createSrcMap = createSrcMap;
this.sourceMapDetailLevel = sourceMapDetailLevel;
this.mappings = createSrcMap ? new ArrayDeque<Mapping>() : null;
this.allMappings = createSrcMap ? new ArrayList<Mapping>() : null;
}
/**
* Maintains a mapping from a given node to the position
* in the source code at which its generated form was
* placed. This position is relative only to the current
* run of the CodeConsumer and will be normalized
* later on by the SourceMap.
*
* @see SourceMap
*/
private static class Mapping {
Node node;
FilePosition start;
FilePosition end;
}
/**
* Starts the source mapping for the given
* node at the current position.
*/
@Override
void startSourceMapping(Node node) {
Preconditions.checkState(sourceMapDetailLevel != null);
Preconditions.checkState(node != null);
if (createSrcMap
&& node.getSourceFileName() != null
&& node.getLineno() > 0
&& sourceMapDetailLevel.
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Lists;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.List;
/**
* Checks for non side effecting statements such as
* <pre>
* var s = "this string is "
* "continued on the next line but you forgot the +";
* x == foo(); // should that be '='?
* foo();; // probably just a stray-semicolon. Doesn't hurt to check though
* </p>
* and generates warnings.
*
*/
final class CheckSideEffects extends AbstractPostOrderCallback
implements HotSwapCompilerPass {
static final DiagnosticType USELESS_CODE_ERROR = DiagnosticType.warning(
"JSC_USELESS_CODE",
"Suspicious code. {0}");
static final String PROTECTOR_FN = "JSCOMPILER_PRESERVE";
private final CheckLevel level;
private final List<Node> problemNodes = Lists.newArrayList();
private final AbstractCompiler compiler;
private final boolean protectSideEffectFreeCode;
CheckSideEffects(AbstractCompiler compiler, CheckLevel level,
boolean protectSideEffectFreeCode) {
this.compiler = compiler;
this.level = level;
this.protectSideEffectFreeCode = protectSideEffectFreeCode;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
// Code with hidden side-effect code is common, for example
// accessing "el.offsetWidth" forces a reflow in browsers, to allow this
// will still allowing local dead code removal in general,
// protect the "side-effect free" code in the source.
//
if (protectSideEffectFreeCode) {
protectSideEffects();
}
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// VOID nodes appear when there are extra semicolons at the BLOCK level.
// I've been unable to think of any cases where this indicates a bug,
// and apparently some people like keeping these semicolons around,
// so we'll allow it.
if (n.isEmpty() ||
n.isComma()) {
return;
}
if (parent == null) {
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.graph;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* A directed graph using linked list within nodes to store edge information.
* <p>
* This implementation favors directed graph operations inherited from <code>
* DirectedGraph</code>.
* Operations from <code>Graph</code> would tends to be slower.
*
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public class LinkedDirectedGraph<N, E>
extends DiGraph<N, E> implements GraphvizGraph {
protected final Map<N, LinkedDirectedGraphNode<N, E>> nodes =
Maps.newHashMap();
@Override
public SubGraph<N, E> newSubGraph() {
return new SimpleSubGraph<N, E>(this);
}
public static <N, E> LinkedDirectedGraph<N, E> createWithoutAnnotations() {
return new LinkedDirectedGraph<N, E>(false, false);
}
public static <N, E> LinkedDirectedGraph<N, E> createWithNodeAnnotations() {
return new LinkedDirectedGraph<N, E>(true, false);
}
public static <N, E> LinkedDirectedGraph<N, E> createWithEdgeAnnotations() {
return new LinkedDirectedGraph<N, E>(false, true);
}
public static <N, E> LinkedDirectedGraph<N, E> create() {
return new LinkedDirectedGraph<N, E>(true, true);
}
private final boolean useNodeAnnotations;
private final boolean useEdgeAnnotations;
protected LinkedDirectedGraph(
boolean useNodeAnnotations, boolean useEdgeAnnotations) {
this.useNodeAnnotations = useNodeAnnotations;
this.useEdgeAnnotations = useEdgeAnnotations;
}
@Override
public void connect(N srcValue, E edgeValue, N destValue) {
LinkedDirectedGraphNode<N, E> src = getNodeOrFail(srcValue);
LinkedDirectedGraphNode<N, E> dest = getNodeOrFail(destValue);
LinkedDirectedGraphEdge<N, E> edge =
useEdgeAnnotations ?
new AnnotatedLinkedDirectedGraphEdge<N, E>(src, edgeValue, dest) :
new LinkedDirectedGraphEdge<N, E>(src,
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> edgeValue, dest);
src.getOutEdges().add(edge);
dest.getInEdges().add(edge);
}
@Override
public void disconnect(N n1, N n2) {
disconnectInDirection(n1, n2);
disconnectInDirection(n2, n1);
}
@Override
public void disconnectInDirection(N srcValue, N destValue) {
LinkedDirectedGraphNode<N, E> src = getNodeOrFail(srcValue);
LinkedDirectedGraphNode<N, E> dest = getNodeOrFail(destValue);
for (DiGraphEdge<?, E> edge : getDirectedGraphEdges(srcValue, destValue)) {
src.getOutEdges().remove(edge);
dest.getInEdges().remove(edge);
}
}
@Override
public Iterable<DiGraphNode<N, E>> getDirectedGraphNodes() {
return Collections.<DiGraphNode<N, E>>unmodifiableCollection(
nodes.values());
}
@Override
public DiGraphNode<N, E> getDirectedGraphNode(N nodeValue) {
return nodes.get(nodeValue);
}
@Override
public GraphNode<N, E> getNode(N nodeValue) {
return getDirectedGraphNode(nodeValue);
}
@Override
public List<DiGraphEdge<N, E>> getInEdges(N nodeValue) {
LinkedDirectedGraphNode<N, E> node = getNodeOrFail(nodeValue);
return Collections.<DiGraphEdge<N, E>>unmodifiableList(node.getInEdges());
}
@Override
public List<DiGraphEdge<N, E>> getOutEdges(N nodeValue) {
LinkedDirectedGraphNode<N, E> node = getNodeOrFail(nodeValue);
return Collections.<DiGraphEdge<N, E>>unmodifiableList(node.getOutEdges());
}
@Override
public DiGraphNode<N, E> createDirectedGraphNode(N nodeValue) {
LinkedDirectedGraphNode<N, E> node = nodes.get(nodeValue);
if (node == null) {
node = useNodeAnnotations ?
new AnnotatedLinkedDirectedGraphNode<N, E>(nodeValue) :
new LinkedDirectedGraphNode<N, E>(nodeValue);
nodes.put(nodeValue, node);
}
return node;
}
@Override
public List<GraphEdge<N, E>> getEdges(N n1, N n2) {
// Since this is a method from a generic graph, edges from both
// directions must be added to the returning list.
List<DiGraphEdge<N, E>> forwardEdges = getDirectedGraphEdges(n1, n2);
List<DiGraphEdge<N, E>> backwardEdges = getDirectedGraphEdges(n2, n1);
int totalSize = forwardEdges.size() + backwardEdges.size();
List<GraphEdge<N, E>> edges = Lists.newArrayListWithCapacity(totalSize);
edges.addAll(forwardEdges);
edges.addAll(backwardEdges);
return edges;
}
@Override
public GraphEdge<N, E> getFirstEdge(N n1, N n2) {
DiGraphNode<N, E> dNode1 = getNodeOrFail(n1);
DiGraphNode<N, E> dNode2 = getNodeOrFail(n2);
for (DiGraphEdge<N, E> outEdge : dNode1.getOutEdges())
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> {
if (outEdge.getDestination() == dNode2) {
return outEdge;
}
}
for (DiGraphEdge<N, E> outEdge : dNode2.getOutEdges()) {
if (outEdge.getDestination() == dNode1) {
return outEdge;
}
}
return null;
}
@Override
public GraphNode<N, E> createNode(N value) {
return createDirectedGraphNode(value);
}
@Override
public List<DiGraphEdge<N, E>> getDirectedGraphEdges(N n1, N n2) {
DiGraphNode<N, E> dNode1 = getNodeOrFail(n1);
DiGraphNode<N, E> dNode2 = getNodeOrFail(n2);
List<DiGraphEdge<N, E>> edges = Lists.newArrayList();
for (DiGraphEdge<N, E> outEdge : dNode1.getOutEdges()) {
if (outEdge.getDestination() == dNode2) {
edges.add(outEdge);
}
}
return edges;
}
@Override
public boolean isConnectedInDirection(N n1, N n2) {
return isConnectedInDirection(n1, Predicates.<E>alwaysTrue(), n2);
}
@Override
public boolean isConnectedInDirection(N n1, E edgeValue, N n2) {
return isConnectedInDirection(n1, Predicates.equalTo(edgeValue), n2);
}
private boolean isConnectedInDirection(N n1, Predicate<E> edgeMatcher, N n2) {
// Verify the nodes.
DiGraphNode<N, E> dNode1 = getNodeOrFail(n1);
DiGraphNode<N, E> dNode2 = getNodeOrFail(n2);
for (DiGraphEdge<N, E> outEdge : dNode1.getOutEdges()) {
if (outEdge.getDestination() == dNode2 &&
edgeMatcher.apply(outEdge.getValue())) {
return true;
}
}
return false;
}
@Override
public List<DiGraphNode<N, E>> getDirectedPredNodes(N nodeValue) {
return getDirectedPredNodes(nodes.get(nodeValue));
}
@Override
public List<DiGraphNode<N, E>> getDirectedSuccNodes(N nodeValue) {
return getDirectedSuccNodes(nodes.get(nodeValue));
}
@Override
public List<DiGraphNode<N, E>> getDirectedPredNodes(
DiGraphNode<N, E> dNode) {
if (dNode == null) {
throw new IllegalArgumentException(dNode + " is null");
}
List<DiGraphNode<N, E>> nodeList = Lists.newArrayList();
for (DiGraphEdge<N, E> edge : dNode.getInEdges()) {
nodeList.add(edge.getSource());
}
return nodeList;
}
@Override
public List<DiGraphNode<N, E>> getDirectedSuccNodes(
DiGraphNode<N, E> dNode) {
if (dNode == null) {
throw new IllegalArgumentException(dNode + " is null");
}
List<DiGraphNode<N, E>> nodeList = Lists.newArrayList();
for (DiGraphEdge<N, E> edge : dNode.getOutEdges()) {
nodeList.add
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>(edge.getDestination());
}
return nodeList;
}
@Override
public List<GraphvizEdge> getGraphvizEdges() {
List<GraphvizEdge> edgeList = Lists.newArrayList();
for (LinkedDirectedGraphNode<N, E> node : nodes.values()) {
for (DiGraphEdge<N, E> edge : node.getOutEdges()) {
edgeList.add((LinkedDirectedGraphEdge<N, E>) edge);
}
}
return edgeList;
}
@Override
public List<GraphvizNode> getGraphvizNodes() {
List<GraphvizNode> nodeList =
Lists.newArrayListWithCapacity(nodes.size());
for (LinkedDirectedGraphNode<N, E> node : nodes.values()) {
nodeList.add(node);
}
return nodeList;
}
@Override
public String getName() {
return "LinkedGraph";
}
@Override
public boolean isDirected() {
return true;
}
@Override
public Collection<GraphNode<N, E>> getNodes() {
return Collections.<GraphNode<N, E>>unmodifiableCollection(nodes.values());
}
@Override
public List<GraphNode<N, E>> getNeighborNodes(N value) {
DiGraphNode<N, E> node = getDirectedGraphNode(value);
return getNeighborNodes(node);
}
public List<GraphNode<N, E>> getNeighborNodes(DiGraphNode<N, E> node) {
List<GraphNode<N, E>> result = Lists.newArrayList();
for (Iterator<GraphNode<N, E>> i =
((LinkedDirectedGraphNode<N, E>) node).neighborIterator();i.hasNext();) {
result.add(i.next());
}
return result;
}
@Override
public Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value) {
LinkedDirectedGraphNode<N, E> node = nodes.get(value);
Preconditions.checkNotNull(node);
return node.neighborIterator();
}
@Override
public List<GraphEdge<N, E>> getEdges() {
List<GraphEdge<N, E>> result = Lists.newArrayList();
for (DiGraphNode<N, E> node : nodes.values()) {
for (DiGraphEdge<N, E> edge : node.getOutEdges()) {
result.add(edge);
}
}
return Collections.unmodifiableList(result);
}
@Override
public int getNodeDegree(N value) {
DiGraphNode<N, E> node = getNodeOrFail(value);
return node.getInEdges().size() + node.getOutEdges().size();
}
/**
* A directed graph node that stores outgoing edges and incoming edges as an
* list within the node itself.
*/
static class LinkedDirectedGraphNode<N, E> implements DiGraphNode<N, E>,
GraphvizNode {
List<DiGraphEdge<N, E>> inEdgeList = Lists.newArrayList();
List<DiGraphEdge<N, E>> outEdgeList =
Lists.newArrayList();
protected final N value;
/**
* Constructor
*
* @param nodeValue Node's value.
*/
LinkedDirectedGraphNode(N nodeValue) {
this.value = nodeValue;
}
@Override
public N getValue() {
return value;
}
@Override
public <A extends Annotation>
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> A getAnnotation() {
throw new UnsupportedOperationException(
"Graph initialized with node annotations turned off");
}
@Override
public void setAnnotation(Annotation data) {
throw new UnsupportedOperationException(
"Graph initialized with node annotations turned off");
}
@Override
public String getColor() {
return "white";
}
@Override
public String getId() {
return "LDN" + hashCode();
}
@Override
public String getLabel() {
return value != null ? value.toString() : "null";
}
@Override
public String toString() {
return getLabel();
}
@Override
public List<DiGraphEdge<N, E>> getInEdges() {
return inEdgeList;
}
@Override
public List<DiGraphEdge<N, E>> getOutEdges() {
return outEdgeList;
}
private Iterator<GraphNode<N, E>> neighborIterator() {
return new NeighborIterator();
}
private class NeighborIterator implements Iterator<GraphNode<N, E>> {
private final Iterator<DiGraphEdge<N, E>> in = inEdgeList.iterator();
private final Iterator<DiGraphEdge<N, E>> out = outEdgeList.iterator();
@Override
public boolean hasNext() {
return in.hasNext() || out.hasNext();
}
@Override
public GraphNode<N, E> next() {
boolean isOut = !in.hasNext();
Iterator<DiGraphEdge<N, E>> curIterator = isOut ? out : in;
DiGraphEdge<N, E> s = curIterator.next();
return isOut ? s.getDestination() : s.getSource();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported.");
}
}
}
/**
* A directed graph node with annotations.
*/
static class AnnotatedLinkedDirectedGraphNode<N, E>
extends LinkedDirectedGraphNode<N, E> {
protected Annotation annotation;
/**
* @param nodeValue Node's value.
*/
AnnotatedLinkedDirectedGraphNode(N nodeValue) {
super(nodeValue);
}
@SuppressWarnings("unchecked")
@Override
public <A extends Annotation> A getAnnotation() {
return (A) annotation;
}
@Override
public void setAnnotation(Annotation data) {
annotation = data;
}
}
/**
* A directed graph edge that stores the source and destination nodes at each
* edge.
*/
static class LinkedDirectedGraphEdge<N, E> implements DiGraphEdge<N, E>,
GraphvizEdge {
private DiGraphNode<N, E> sourceNode;
private DiGraphNode<N, E> destNode;
protected final E value;
/**
* Constructor.
*
* @param edgeValue Edge Value.
*/
LinkedDirectedGraphEdge(DiGraphNode<N, E> sourceNode,
E edgeValue, DiGraphNode<N, E> destNode) {
this.value = edgeValue;
this.sourceNode = sourceNode;
this.destNode = destNode;
}
@Override
public DiGraphNode<N, E> getSource() {
return sourceNode;
}
@Override
public DiGraphNode<N, E> getDestination() {
return destNode;
}
@Override
public void setDestination(DiGraphNode<N, E> node) {
destNode = node;
}
@Override
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> public void setSource(DiGraphNode<N, E> node) {
sourceNode = node;
}
@Override
public E getValue() {
return value;
}
@Override
public <A extends Annotation> A getAnnotation() {
throw new UnsupportedOperationException(
"Graph initialized with edge annotations turned off");
}
@Override
public void setAnnotation(Annotation data) {
throw new UnsupportedOperationException(
"Graph initialized with edge annotations turned off");
}
@Override
public String getColor() {
return "black";
}
@Override
public String getLabel() {
return value != null ? value.toString() : "null";
}
@Override
public String getNode1Id() {
return ((LinkedDirectedGraphNode<N, E>) sourceNode).getId();
}
@Override
public String getNode2Id() {
return ((LinkedDirectedGraphNode<N, E>) destNode).getId();
}
@Override
public String toString() {
return sourceNode.toString() + " -> " + destNode.toString();
}
@Override
public GraphNode<N, E> getNodeA() {
return sourceNode;
}
@Override
public GraphNode<N, E> getNodeB() {
return destNode;
}
}
/**
* A directed graph edge that stores the source and destination nodes at each
* edge.
*/
static class AnnotatedLinkedDirectedGraphEdge<N, E>
extends LinkedDirectedGraphEdge<N, E> {
protected Annotation annotation;
/**
* Constructor.
*
* @param edgeValue Edge Value.
*/
AnnotatedLinkedDirectedGraphEdge(DiGraphNode<N, E> sourceNode,
E edgeValue, DiGraphNode<N, E> destNode) {
super(sourceNode, edgeValue, destNode);
}
@SuppressWarnings("unchecked")
@Override
public <A extends Annotation> A getAnnotation() {
return (A) annotation;
}
@Override
public void setAnnotation(Annotation data) {
annotation = data;
}
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>Cake|null) where only Cake is
// allowed, that doesn't mean we should invalidate all Cakes.
private final List<TypeMismatch> mismatches = Lists.newArrayList();
// User warnings
private static final String FOUND_REQUIRED =
"{0}\n" +
"found : {1}\n" +
"required: {2}";
static final DiagnosticType INVALID_CAST =
DiagnosticType.warning("JSC_INVALID_CAST",
"invalid cast - must be a subtype or supertype\n" +
"from: {0}\n" +
"to : {1}");
static final DiagnosticType TYPE_MISMATCH_WARNING =
DiagnosticType.warning(
"JSC_TYPE_MISMATCH",
"{0}");
static final DiagnosticType MISSING_EXTENDS_TAG_WARNING =
DiagnosticType.warning(
"JSC_MISSING_EXTENDS_TAG",
"Missing @extends tag on type {0}");
static final DiagnosticType DUP_VAR_DECLARATION =
DiagnosticType.warning("JSC_DUP_VAR_DECLARATION",
"variable {0} redefined with type {1}, " +
"original definition at {2}:{3} with type {4}");
static final DiagnosticType HIDDEN_PROPERTY_MISMATCH =
DiagnosticType.warning("JSC_HIDDEN_PROPERTY_MISMATCH",
"mismatch of the {0} property type and the type " +
"of the property it overrides from superclass {1}\n" +
"original: {2}\n" +
"override: {3}");
static final DiagnosticType INTERFACE_METHOD_NOT_IMPLEMENTED =
DiagnosticType.warning(
"JSC_INTERFACE_METHOD_NOT_IMPLEMENTED",
"property {0} on interface {1} is not implemented by type {2}");
static final DiagnosticType HIDDEN_INTERFACE_PROPERTY_MISMATCH =
DiagnosticType.warning(
"JSC_HIDDEN_INTERFACE_PROPERTY_MISMATCH",
"mismatch of the {0} property type and the type " +
"of the property it overrides from interface {1}\n" +
"original: {2}\n" +
"override: {3}");
static final DiagnosticType UNKNOWN_TYPEOF_VALUE =
DiagnosticType.warning("JSC_UNKNOWN_TYPEOF_VALUE", "unknown type: {0}");
static final DiagnosticGroup ALL_DIAGNOSTICS = new DiagnosticGroup(
INVALID_CAST,
TYPE_MISMATCH_WARNING,
MISSING_EXTENDS_TAG_WARNING,
DUP_VAR_DECLARATION,
HIDDEN_PROPERTY_MISMATCH,
INTERFACE_METHOD_NOT_IMPLEMENTED,
HIDDEN_INTERFACE_PROPERTY_MISMATCH,
UNKNOWN_TYPEOF_VALUE);
TypeValidator(AbstractCompiler compiler) {
this.compiler = compiler;
this.typeRegistry = compiler.getTypeRegistry();
this.allValueTypes = typeRegistry.createUnionType(
STRING_TYPE, NUMBER_TYPE, BOOLEAN_TYPE, NULL_TYPE, VOID_TYPE);
this.nullOrUndefined = typeRegistry.createUnionType(
NULL_TYPE, VOID_TYPE);
}
/**
* Gets a list of type violations.
*
* For each violation, one element is the expected type and the other is
* the type that is actually found. Order is not significant.
*/
Iterable<TypeMismatch> getMismatches() {
return mismatches;
}
void setShouldReport(boolean report) {
this.shouldReport = report;
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.graph.LatticeElement;
import java.util.List;
/**
* Defines a way join a list of LatticeElements.
*/
interface JoinOp<L extends LatticeElement> extends Function<List<L>, L> {
/**
* An implementation of {@code JoinOp} that makes it easy to join to
* lattice elements at a time.
*/
static abstract class BinaryJoinOp<L extends LatticeElement>
implements JoinOp<L> {
@Override
public final L apply(List<L> values) {
Preconditions.checkArgument(!values.isEmpty());
int size = values.size();
if (size == 1) {
return values.get(0);
} else if (size == 2) {
return apply(values.get(0), values.get(1));
} else {
int mid = computeMidPoint(size);
return apply(
apply(values.subList(0, mid)),
apply(values.subList(mid, size)));
}
}
/**
* Creates a new lattice that will be the join of two input lattices.
*
* @return The join of {@code latticeA} and {@code latticeB}.
*/
abstract L apply(L latticeA, L latticeB);
/**
* Finds the midpoint of a list. The function will favor two lists of
* even length instead of two lists of the same odd length. The list
* must be at least length two.
*
* @param size Size of the list.
*/
static int computeMidPoint(int size) {
int midpoint = size >>> 1;
if (size > 4) {
/* Any list longer than 4 should prefer an even split point
* over the true midpoint, so that [0,6] splits at 2, not 3. */
midpoint &= -2; // (0xfffffffe) clears low bit so midpoint is even
}
return midpoint;
}
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
replacePlaceHolders(ScriptRuntime.getMessage0("msg.bad.jsdoc.tag")),
BAD_JSDOC_ANNOTATION,
// Type annotation errors.
Pattern.compile("^Bad type annotation.*"),
TYPE_PARSE_ERROR);
}
public static com.google.javascript.rhino.head.ErrorReporter
forNewRhino(AbstractCompiler compiler) {
return new NewRhinoErrorReporter(compiler);
}
public static ErrorReporter forOldRhino(AbstractCompiler compiler) {
return new OldRhinoErrorReporter(compiler);
}
void warningAtLine(String message, String sourceName, int line,
int lineOffset) {
compiler.report(
makeError(message, sourceName, line, lineOffset, CheckLevel.WARNING));
}
void errorAtLine(String message, String sourceName, int line,
int lineOffset) {
compiler.report(
makeError(message, sourceName, line, lineOffset, CheckLevel.ERROR));
}
private JSError makeError(String message, String sourceName, int line,
int lineOffset, CheckLevel defaultLevel) {
// Try to see if the message is one of the rhino errors we want to
// expose as DiagnosticType by matching it with the regex key.
for (Entry<Pattern, DiagnosticType> entry : typeMap.entrySet()) {
if (entry.getKey().matcher(message).matches()) {
return JSError.make(
sourceName, line, lineOffset, entry.getValue(), message);
}
}
return JSError.make(sourceName, line, lineOffset, defaultLevel,
PARSE_ERROR, message);
}
private static class OldRhinoErrorReporter extends RhinoErrorReporter
implements ErrorReporter {
private OldRhinoErrorReporter(AbstractCompiler compiler) {
super(compiler);
}
@Override
public void error(String message, String sourceName, int line,
int lineOffset) {
super.errorAtLine(message, sourceName, line, lineOffset);
}
@Override
public void warning(String message, String sourceName, int line,
int lineOffset) {
super.warningAtLine(message, sourceName, line, lineOffset);
}
}
private static class NewRhinoErrorReporter extends RhinoErrorReporter
implements com.google.javascript.rhino.head.ast.IdeErrorReporter {
private NewRhinoErrorReporter(AbstractCompiler compiler) {
super(compiler);
}
@Override
public com.google.javascript.rhino.head.EvaluatorException
runtimeError(String message, String sourceName, int line,
String lineSource, int lineOffset) {
return new com.google.javascript.rhino.head.EvaluatorException(
message, sourceName, line, lineSource, lineOffset);
}
@Override
public void error(String message, String sourceName, int line,
String sourceLine, int lineOffset) {
super.errorAtLine(message, sourceName, line, lineOffset);
}
@Override
public void error(String message, String sourceName,
int offset, int length) {
int line = 1;
int column = 0;
SourceFile file = this.compiler.getSourceFileByName(sourceName);
if (file != null) {
line = file.getLineOfOffset(offset);
column = file.getColumnOfOffset(offset);
}
super.errorAtLine(message, sourceName, line, column);
}
@
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>NOT_ASSIGNABLE_ERROR",
"@define variable {0} cannot be reassigned due to code at {1}.");
private static final MessageFormat REASON_DEFINE_NOT_ASSIGNABLE =
new MessageFormat("line {0} of {1}");
/**
* Create a pass that overrides define constants.
*
* TODO(nicksantos): Write a builder to help JSCompiler induce
* {@code replacements} from command-line flags
*
* @param replacements A hash table of names of defines to their replacements.
* All replacements <b>must</b> be literals.
*/
ProcessDefines(AbstractCompiler compiler, Map<String, Node> replacements) {
this.compiler = compiler;
dominantReplacements = replacements;
}
/**
* Injects a pre-computed global namespace, so that the same namespace
* can be re-used for multiple check passes. Returns {@code this} for
* easy chaining.
*/
ProcessDefines injectNamespace(GlobalNamespace namespace) {
this.namespace = namespace;
return this;
}
@Override
public void process(Node externs, Node root) {
if (namespace == null) {
namespace = new GlobalNamespace(compiler, root);
}
overrideDefines(collectDefines(root, namespace));
}
private void overrideDefines(Map<String, DefineInfo> allDefines) {
boolean changed = false;
for (Map.Entry<String, DefineInfo> def : allDefines.entrySet()) {
String defineName = def.getKey();
DefineInfo info = def.getValue();
Node inputValue = dominantReplacements.get(defineName);
Node finalValue = inputValue != null ?
inputValue : info.getLastValue();
if (finalValue != info.initialValue) {
info.initialValueParent.replaceChild(
info.initialValue, finalValue.cloneTree());
compiler.addToDebugLog("Overriding @define variable " + defineName);
changed = changed ||
finalValue.getType() != info.initialValue.getType() ||
!finalValue.isEquivalentTo(info.initialValue);
}
}
if (changed) {
compiler.reportCodeChange();
}
Set<String> unusedReplacements = dominantReplacements.keySet();
unusedReplacements.removeAll(allDefines.keySet());
unusedReplacements.removeAll(KNOWN_DEFINES);
for (String unknownDefine : unusedReplacements) {
compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine));
}
}
private static String format(MessageFormat format, Object... params) {
return format.format(params);
}
/**
* Only defines of literal number, string, or boolean are supported.
*/
private boolean isValidDefineType(JSTypeExpression expression) {
JSType type = expression.evaluate(null, compiler.getTypeRegistry());
return !type.isUnknownType() && type.isSubtype(
compiler.getTypeRegistry().getNativeType(
JSTypeNative.NUMBER_STRING_BOOLEAN));
}
/**
* Finds all defines, and creates a {@link DefineInfo} data structure for
* each one.
* @return A map of {@link DefineInfo} structures, keyed by name.
*/
private Map<String, DefineInfo> collectDefines(Node root,
GlobalNamespace namespace) {
// Find all the global names with a @define annotation
List<Name> allDefines = Lists.newArrayList();
for (Name name : namespace.getNameIndex().values
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>()) {
Ref decl = name.getDeclaration();
if (name.docInfo != null && name.docInfo.isDefine()) {
// Process defines should not depend on check types being enabled,
// so we look for the JSDoc instead of the inferred type.
if (isValidDefineType(name.docInfo.getType())) {
allDefines.add(name);
} else {
JSError error = JSError.make(
decl.getSourceName(),
decl.node, INVALID_DEFINE_TYPE_ERROR);
compiler.report(error);
}
} else {
for (Ref ref : name.getRefs()) {
if (ref == decl) {
// Declarations were handled above.
continue;
}
Node n = ref.node;
Node parent = ref.node.getParent();
JSDocInfo info = n.getJSDocInfo();
if (info == null &&
parent.isVar() && parent.hasOneChild()) {
info = parent.getJSDocInfo();
}
if (info != null && info.isDefine()) {
allDefines.add(name);
break;
}
}
}
}
CollectDefines pass = new CollectDefines(compiler, allDefines);
NodeTraversal.traverse(compiler, root, pass);
return pass.getAllDefines();
}
/**
* Finds all assignments to @defines, and figures out the last value of
* the @define.
*/
private static final class CollectDefines implements Callback {
private final AbstractCompiler compiler;
private final Map<String, DefineInfo> assignableDefines;
private final Map<String, DefineInfo> allDefines;
private final Map<Node, RefInfo> allRefInfo;
// A hack that allows us to remove ASSIGN/VAR statements when
// we're currently visiting one of the children of the assign.
private Node lvalueToRemoveLater = null;
// A stack tied to the node traversal, to keep track of whether
// we're in a conditional block. If 1 is at the top, assignment to
// a define is allowed. Otherwise, it's not allowed.
private final Deque<Integer> assignAllowed;
CollectDefines(AbstractCompiler compiler, List<Name> listOfDefines) {
this.compiler = compiler;
this.allDefines = Maps.newHashMap();
assignableDefines = Maps.newHashMap();
assignAllowed = new ArrayDeque<Integer>();
assignAllowed.push(1);
// Create a map of references to defines keyed by node for easy lookup
allRefInfo = Maps.newHashMap();
for (Name name : listOfDefines) {
Ref decl = name.getDeclaration();
if (decl != null) {
allRefInfo.put(decl.node,
new RefInfo(decl, name));
}
for (Ref ref : name.getRefs()) {
if (ref == decl) {
// Declarations were handled above.
continue;
}
// If there's a TWIN def, only put one of the twins in.
if (ref.getTwin() == null || !ref.getTwin().isSet()) {
allRefInfo.put(ref.node, new RefInfo(ref, name));
}
}
}
}
/**
* Get a map of {@link DefineInfo} structures, keyed by the name of
* the define.
*/
Map<String, DefineInfo> getAllDefines() {
return allDefines;
}
/**
* Keeps track of whether the traversal is in a conditional branch.
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> }
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.inputId = null;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (inputId != null) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void traverse(Node root) {
try {
inputId = NodeUtil.getInputId(root);
sourceName = "";
curNode = root;
pushScope(root);
traverseBranch(root, null);
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
public void traverseRoots(Node ... roots) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
try {
Node scopeRoot = roots.get(0).getParent();
Preconditions.checkState(scopeRoot != null);
inputId = NodeUtil.getInputId(scopeRoot);
sourceName = "";
curNode = scopeRoot;
pushScope(scopeRoot);
for (Node root : roots) {
Preconditions.checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot);
}
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
String sourceFileName = getBestSourceFileName(n);
if (sourceFileName == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceFileName, lineNumber);
if (src == null) {
src = MISSING_SOURCE;
}
return sourceFileName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
public Node getCurrentNode() {
return curNode;
}
/**
* Traverses a node recursively.
*/
public static void traverse(
AbstractCompiler compiler, Node root, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverse(root);
}
/**
* Traverses a list of node trees.
*/
public static void traverseRoots(
AbstractCompiler compiler, List<Node> roots, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
public static void traverseRoots(
AbstractCompiler compiler, Callback cb, Node ... roots) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
/**
* Traverses a branch.
*/
@SuppressWarnings("fallthrough")
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
inputId = n.getInputId();
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) return;
switch (type) {
case Token.FUNCTION:
traverseFunction(n, parent);
break;
default:
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
break;
}
curNode = n;
callback.visit(this, n, parent);
}
/**
* Traverses a function.
*/
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.isFunction());
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null &&
body.isBlock(), body);
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for the last instance of a function node. */
@SuppressWarnings("unchecked")
public Node getEnclosingFunction() {
if (scopes.size() + scopeRoots.size() < 2) {
return null;
} else {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Node node) {
Preconditions.checkState(curNode != null);
scopeRoots.push(node);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Creates a
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> new scope (e.g. when entering a function). */
private void pushScope(Scope s) {
Preconditions.checkState(curNode != null);
scopes.push(s);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Pops back to the previous scope (e.g. when leaving a function). */
private void popScope() {
if (scopeCallback != null) {
scopeCallback.exitScope(this);
}
if (scopeRoots.isEmpty()) {
scopes.pop();
} else {
scopeRoots.pop();
}
cfgs.pop();
}
/** Gets the current scope. */
public Scope getScope() {
Scope scope = scopes.isEmpty() ? null : scopes.peek();
if (scopeRoots.isEmpty()) {
return scope;
}
Iterator<Node> it = scopeRoots.descendingIterator();
while (it.hasNext()) {
scope = scopeCreator.createScope(it.next(), scope);
scopes.push(scope);
}
scopeRoots.clear();
return scope;
}
/** Gets the control flow graph for the current JS scope. */
public ControlFlowGraph<Node> getControlFlowGraph() {
if (cfgs.peek() == null) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
cfa.process(null, getScopeRoot());
cfgs.pop();
cfgs.push(cfa.getCfg());
}
return cfgs.peek();
}
/** Returns the current scope's root. */
public Node getScopeRoot() {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
/**
* Determines whether the traversal is currently in the global scope.
*/
boolean inGlobalScope() {
return getScopeDepth() <= 1;
}
int getScopeDepth() {
return scopes.size() + scopeRoots.size();
}
public boolean hasScope() {
return !(scopes.isEmpty() && scopeRoots.isEmpty());
}
/** Reports a diagnostic (error or warning) */
public void report(Node n, DiagnosticType diagnosticType,
String... arguments) {
JSError error = JSError.make(getBestSourceFileName(n),
n, diagnosticType, arguments);
compiler.report(error);
}
private static String getSourceName(Node n) {
String name = n.getSourceFileName();
return name == null ? "" : name;
}
InputId getInputId() {
return inputId;
}
/**
* Creates a JSError during NodeTraversal.
*
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public JSError makeError(Node n, CheckLevel level, DiagnosticType type,
String... arguments) {
return JSError.make(getBestSourceFileName(n), n, level, type, arguments);
}
/**
* Creates a JSError during NodeTraversal.
*
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public JSError makeError(Node n, DiagnosticType type, String... arguments) {
return JSError.make(
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* John Lenz
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino;
import com.google.common.base.Preconditions;
import java.util.List;
/**
* An AST construction helper class
* @author johnlenz@google.com (John Lenz)
*/
public class IR {
private IR() {}
public static Node empty() {
return new Node(Token.EMPTY);
}
public static Node function(Node name, Node params, Node body) {
Preconditions.checkState(name.isName());
Preconditions.checkState(params.isParamList());
Preconditions.checkState(body.isBlock());
return new Node(Token.FUNCTION, name, params, body);
}
public static Node paramList() {
return new Node(Token.PARAM_LIST);
}
public static Node paramList(Node param) {
Preconditions.checkState(param.isName());
return new Node(Token.PARAM_LIST, param);
}
public static Node paramList(Node ... params) {
Node paramList = paramList();
for (Node param : params) {
Preconditions.checkState(param.isName());
paramList.addChildToBack(param);
}
return paramList;
}
public static Node paramList(List<Node> params) {
Node paramList = paramList();
for (Node param : params) {
Preconditions.checkState(param.isName());
paramList.addChildToBack(param);
}
return paramList;
}
public static Node block() {
Node block = new Node(Token.BLOCK);
return block;
}
public static Node block(Node stmt) {
Preconditions.checkState(mayBeStatement(stmt
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>));
Node block = new Node(Token.BLOCK, stmt);
return block;
}
public static Node block(Node ... stmts) {
Node block = block();
for (Node stmt : stmts) {
Preconditions.checkState(mayBeStatement(stmt));
block.addChildToBack(stmt);
}
return block;
}
public static Node block(List<Node> stmts) {
Node paramList = block();
for (Node stmt : stmts) {
Preconditions.checkState(mayBeStatement(stmt));
paramList.addChildToBack(stmt);
}
return paramList;
}
private static Node blockUnchecked(Node stmt) {
return new Node(Token.BLOCK, stmt);
}
public static Node script() {
// TODO(johnlenz): finish setting up the SCRIPT node
Node block = new Node(Token.SCRIPT);
return block;
}
public static Node script(Node ... stmts) {
Node block = script();
for (Node stmt : stmts) {
Preconditions.checkState(mayBeStatementNoReturn(stmt));
block.addChildToBack(stmt);
}
return block;
}
public static Node script(List<Node> stmts) {
Node paramList = script();
for (Node stmt : stmts) {
Preconditions.checkState(mayBeStatementNoReturn(stmt));
paramList.addChildToBack(stmt);
}
return paramList;
}
public static Node var(Node name, Node value) {
Preconditions.checkState(name.isName() && !name.hasChildren());
Preconditions.checkState(mayBeExpression(value));
name.addChildToFront(value);
return var(name);
}
public static Node var(Node name) {
Preconditions.checkState(name.isName());
return new Node(Token.VAR, name);
}
public static Node returnNode() {
return new Node(Token.RETURN);
}
public static Node returnNode(Node expr) {
Preconditions.checkState(mayBeExpression(expr));
return new Node(Token.RETURN, expr);
}
public static Node throwNode(Node expr) {
Preconditions.checkState(mayBeExpression(expr));
return new Node(Token.THROW, expr);
}
public static Node exprResult(Node expr) {
Preconditions.checkState(mayBeExpression(expr));
return new Node(Token.EXPR_RESULT, expr);
}
public static Node ifNode(Node cond, Node then) {
Preconditions.checkState(mayBeExpression(cond));
Preconditions.checkState(then.isBlock());
return new Node(Token.IF, cond, then);
}
public static Node ifNode(Node cond, Node then, Node elseNode) {
Preconditions.checkState(mayBeExpression(cond));
Preconditions.checkState(then.isBlock());
Preconditions.checkState(elseNode.isBlock());
return new Node(Token.IF, cond, then, elseNode);
}
public static Node doNode(Node body, Node cond) {
Preconditions.checkState(body.isBlock());
Preconditions.checkState(mayBeExpression(cond));
return new Node(Token.DO, body, cond);
}
public static Node forIn(Node target, Node cond, Node body) {
Preconditions.checkState(target.isVar() || mayBeExpression(target));
Preconditions.checkState(mayBeExpression(cond));
Preconditions.checkState
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>Str) {
this.appNameStr = appNameStr;
}
/** Record function information */
public boolean recordFunctionInformation;
public boolean generateExports;
/** Map used in the renaming of CSS class names. */
public CssRenamingMap cssRenamingMap;
/** Process instances of goog.testing.ObjectPropertyString. */
boolean processObjectPropertyString;
/** Replace id generators */
boolean replaceIdGenerators = true; // true by default for legacy reasons.
/** Id generators to replace. */
Set<String> idGenerators;
/** Configuration strings */
List<String> replaceStringsFunctionDescriptions;
String replaceStringsPlaceholderToken;
// A list of strings that should not be used as replacements
Set<String> replaceStringsReservedStrings;
/** List of properties that we report invalidation errors for. */
Map<String, CheckLevel> propertyInvalidationErrors;
/** Transform AMD to CommonJS modules. */
boolean transformAMDToCJSModules = false;
/** Rewrite CommonJS modules so that they can be concatenated together. */
boolean processCommonJSModules = false;
/** CommonJS module prefix. */
String commonJSModulePathPrefix =
ProcessCommonJSModules.DEFAULT_FILENAME_PREFIX;
//--------------------------------
// Output options
//--------------------------------
/** Output in pretty indented format */
public boolean prettyPrint;
/** Line break the output a bit more aggressively */
public boolean lineBreak;
/** Prefer line breaks at end of file */
public boolean preferLineBreakAtEndOfFile;
/** Prints a separator comment before each JS script */
public boolean printInputDelimiter;
/** The string to use as the separator for printInputDelimiter */
public String inputDelimiter = "// Input %num%";
String reportPath;
/** Where to save a report of global name usage */
public void setReportPath(String reportPath) {
this.reportPath = reportPath;
}
TracerMode tracer;
public TracerMode getTracerMode() {
return tracer;
}
public void setTracerMode(TracerMode mode) {
tracer = mode;
}
private boolean colorizeErrorOutput;
public ErrorFormat errorFormat;
private ComposeWarningsGuard warningsGuard = new ComposeWarningsGuard();
int summaryDetailLevel = 1;
int lineLengthThreshold = CodePrinter.DEFAULT_LINE_LENGTH_THRESHOLD;
//--------------------------------
// Special Output Options
//--------------------------------
/**
* Whether the exports should be made available via {@link Result} after
* compilation. This is implicitly true if {@link #externExportsPath} is set.
*/
private boolean externExports;
/** The output path for the created externs file. */
String externExportsPath;
String nameReferenceReportPath;
/** Where to save a cross-reference report from the name reference graph */
public void setNameReferenceReportPath(String filePath) {
nameReferenceReportPath = filePath;
}
String nameReferenceGraphPath;
/** Where to save the name reference graph */
public void setNameReferenceGraphPath(String filePath) {
nameReferenceGraphPath = filePath;
}
//--------------------------------
// Debugging Options
//--------------------------------
/** The output path for the source map. */
public String sourceMapOutputPath;
/** The detail level for the generated source map. */
public SourceMap.DetailLevel sourceMapDetailLevel =
SourceMap.DetailLevel.SYMBOLS;
/** The source map file format */
public SourceMap.Format sourceMapFormat =
SourceMap.Format.DEFAULT;
public List<SourceMap.LocationMapping> sourceMapLocationMappings =
Collections.emptyList();
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> false;
shadowVariables = false;
renamePrefix = null;
aliasKeywords = false;
collapseProperties = false;
collapsePropertiesOnExternTypes = false;
collapseObjectLiterals = false;
devirtualizePrototypeMethods = false;
disambiguateProperties = false;
ambiguateProperties = false;
anonymousFunctionNaming = AnonymousFunctionNamingPolicy.OFF;
exportTestFunctions = false;
// Alterations
runtimeTypeCheck = false;
runtimeTypeCheckLogFunction = null;
ignoreCajaProperties = false;
syntheticBlockStartMarker = null;
syntheticBlockEndMarker = null;
locale = null;
markAsCompiled = false;
removeTryCatchFinally = false;
closurePass = false;
jqueryPass = false;
removeAbstractMethods = true;
removeClosureAsserts = false;
stripTypes = Collections.emptySet();
stripNameSuffixes = Collections.emptySet();
stripNamePrefixes = Collections.emptySet();
stripTypePrefixes = Collections.emptySet();
customPasses = null;
markNoSideEffectCalls = false;
defineReplacements = Maps.newHashMap();
tweakProcessing = TweakProcessing.OFF;
tweakReplacements = Maps.newHashMap();
moveFunctionDeclarations = false;
instrumentationTemplate = null;
appNameStr = "";
recordFunctionInformation = false;
generateExports = false;
cssRenamingMap = null;
processObjectPropertyString = false;
idGenerators = Collections.emptySet();
replaceStringsFunctionDescriptions = Collections.emptyList();
replaceStringsPlaceholderToken = "";
replaceStringsReservedStrings = Collections.emptySet();
propertyInvalidationErrors = Maps.newHashMap();
// Output
printInputDelimiter = false;
prettyPrint = false;
lineBreak = false;
preferLineBreakAtEndOfFile = false;
reportPath = null;
tracer = TracerMode.OFF;
colorizeErrorOutput = false;
errorFormat = ErrorFormat.SINGLELINE;
debugFunctionSideEffectsPath = null;
externExports = false;
nameReferenceReportPath = null;
nameReferenceGraphPath = null;
// Debugging
aliasHandler = NULL_ALIAS_TRANSFORMATION_HANDLER;
errorHandler = null;
}
/**
* @return Whether to attempt to remove unused class properties
*/
public boolean isRemoveUnusedClassProperties() {
return removeUnusedClassProperties;
}
/**
* @param removeUnusedClassProperties Whether to attempt to remove
* unused class properties
*/
public void setRemoveUnusedClassProperties(boolean removeUnusedClassProperties) {
this.removeUnusedClassProperties = removeUnusedClassProperties;
}
/**
* Returns the map of define replacements.
*/
public Map<String, Node> getDefineReplacements() {
return getReplacementsHelper(defineReplacements);
}
/**
* Returns the map of tweak replacements.
*/
public Map<String, Node> getTweakReplacements() {
return getReplacementsHelper(tweakReplacements);
}
/**
* Creates a map of String->Node from a map of String->Number/String/Boolean.
*/
private static Map<String, Node> getReplacementsHelper(
Map<String, Object> source) {
Map<String, Node> map = Maps.newHashMap();
for (Map.Entry<String, Object> entry : source.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
if (value instanceof Boolean) {
map.put(name, NodeUtil.booleanNode(((Boolean) value
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>Variables(Reach reach) {
switch (reach) {
case ALL:
this.removeUnusedVars = true;
this.removeUnusedLocalVars = true;
break;
case LOCAL_ONLY:
this.removeUnusedVars = false;
this.removeUnusedLocalVars = true;
break;
case NONE:
this.removeUnusedVars = false;
this.removeUnusedLocalVars = false;
break;
default:
throw new IllegalStateException("unexpected");
}
}
/**
* Sets the functions whose debug strings to replace.
*/
public void setReplaceStringsConfiguration(
String placeholderToken, List<String> functionDescriptors) {
this.replaceStringsPlaceholderToken = placeholderToken;
this.replaceStringsFunctionDescriptions =
Lists.newArrayList(functionDescriptors);
}
@Deprecated
public void setRewriteNewDateGoogNow(boolean rewrite) {
}
public void setRemoveAbstractMethods(boolean remove) {
this.removeAbstractMethods = remove;
}
public void setRemoveClosureAsserts(boolean remove) {
this.removeClosureAsserts = remove;
}
/**
* If true, name anonymous functions only. All other passes will be skipped.
*/
public void setNameAnonymousFunctionsOnly(boolean value) {
this.nameAnonymousFunctionsOnly = value;
}
public void setColorizeErrorOutput(boolean colorizeErrorOutput) {
this.colorizeErrorOutput = colorizeErrorOutput;
}
public boolean shouldColorizeErrorOutput() {
return colorizeErrorOutput;
}
/**
* If true, chain calls to functions that return this.
*/
public void setChainCalls(boolean value) {
this.chainCalls = value;
}
/**
* If true, accept `const' keyword.
*/
public void setAcceptConstKeyword(boolean value) {
this.acceptConstKeyword = value;
}
/**
* Enable run-time type checking, which adds JS type assertions for debugging.
*
* @param logFunction A JS function to be used for logging run-time type
* assertion failures.
*/
public void enableRuntimeTypeCheck(String logFunction) {
this.runtimeTypeCheck = true;
this.runtimeTypeCheckLogFunction = logFunction;
}
public void disableRuntimeTypeCheck() {
this.runtimeTypeCheck = false;
}
public void setGenerateExports(boolean generateExports) {
this.generateExports = generateExports;
}
public void setCodingConvention(CodingConvention codingConvention) {
this.codingConvention = codingConvention;
}
public CodingConvention getCodingConvention() {
return codingConvention;
}
/**
* Sets dependency options. See the DependencyOptions class for more info.
* This supersedes manageClosureDependencies.
*/
public void setDependencyOptions(DependencyOptions options) {
Preconditions.checkNotNull(options);
this.dependencyOptions = options;
}
/**
* Sort inputs by their goog.provide/goog.require calls, and prune inputs
* whose symbols are not required.
*/
public void setManageClosureDependencies(boolean newVal) {
dependencyOptions.setDependencySorting(
newVal || dependencyOptions.shouldSortDependencies());
dependencyOptions.setDependencyPruning(
newVal || dependencyOptions.shouldPruneDependencies());
dependencyOptions.setMoocherDropping(false);
manageClosureDependencies = newVal;
}
/**
* Sort inputs by their goog.provide/goog.require calls.
*
* @param entryPoints Entry points to the program. Must be goog.provide'd
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
* symbols. Any goog.provide'd symbols that are not a transitive
* dependency of the entry points will be deleted.
* Files without goog.provides, and their dependencies,
* will always be left in.
*/
public void setManageClosureDependencies(List<String> entryPoints) {
Preconditions.checkNotNull(entryPoints);
setManageClosureDependencies(true);
dependencyOptions.setEntryPoints(entryPoints);
}
/**
* Controls how detailed the compilation summary is. Values:
* 0 (never print summary), 1 (print summary only if there are
* errors or warnings), 2 (print summary if type checking is on,
* see --check_types), 3 (always print summary). The default level
* is 1
*/
public void setSummaryDetailLevel(int summaryDetailLevel) {
this.summaryDetailLevel = summaryDetailLevel;
}
/**
* @deprecated replaced by {@link #setExternExports}
*/
@Deprecated
public void enableExternExports(boolean enabled) {
this.externExports = enabled;
}
public void setExtraAnnotationNames(Set<String> extraAnnotationNames) {
this.extraAnnotationNames = Sets.newHashSet(extraAnnotationNames);
}
public boolean isExternExportsEnabled() {
return externExports;
}
/**
* Sets the output charset by name.
*/
public void setOutputCharset(String charsetName) {
this.outputCharset = charsetName;
}
/**
* Sets how goog.tweak calls are processed.
*/
public void setTweakProcessing(TweakProcessing tweakProcessing) {
this.tweakProcessing = tweakProcessing;
}
public TweakProcessing getTweakProcessing() {
return tweakProcessing;
}
/**
* Sets how goog.tweak calls are processed.
*/
public void setLanguageIn(LanguageMode languageIn) {
this.languageIn = languageIn;
this.languageOut = languageIn;
}
public LanguageMode getLanguageIn() {
return languageIn;
}
public LanguageMode getLanguageOut() {
return languageOut;
}
/**
* Whether to include "undefined" in the default types.
* For example:
* "{Object}" is normally "Object|null" becomes "Object|null|undefined"
* "{?string}" is normally "string|null" becomes "string|null|undefined"
* In either case "!" annotated types excluded both null and undefined.
*/
public void setLooseTypes(boolean looseTypes) {
this.looseTypes = looseTypes;
}
@Override
public Object clone() throws CloneNotSupportedException {
CompilerOptions clone = (CompilerOptions) super.clone();
// TODO(bolinfest): Add relevant custom cloning.
return clone;
}
public void setAliasTransformationHandler(
AliasTransformationHandler changes) {
this.aliasHandler = changes;
}
public AliasTransformationHandler getAliasTransformationHandler() {
return this.aliasHandler;
}
/**
* Set a custom handler for warnings and errors.
*
* This is mostly used for piping the warnings and errors to
* a file behind the scenes.
*
* If you want to filter warnings and errors, you should use a WarningsGuard.
*
* If you want to change how warnings and errors are reported to the user,
* you should set a ErrorManager on the Compiler. An ErrorManager is
* intended to summarize the errors for a single compile job.
*/
public void set
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>Names) {
this.gatherCssNames = gatherCssNames;
}
public void setStripTypes(Set<String> stripTypes) {
this.stripTypes = stripTypes;
}
public void setStripNameSuffixes(Set<String> stripNameSuffixes) {
this.stripNameSuffixes = stripNameSuffixes;
}
public void setStripNamePrefixes(Set<String> stripNamePrefixes) {
this.stripNamePrefixes = stripNamePrefixes;
}
public void setStripTypePrefixes(Set<String> stripTypePrefixes) {
this.stripTypePrefixes = stripTypePrefixes;
}
public void setCustomPasses(Multimap<CustomPassExecutionTime, CompilerPass> customPasses) {
this.customPasses = customPasses;
}
public void setMarkNoSideEffectCalls(boolean markNoSideEffectCalls) {
this.markNoSideEffectCalls = markNoSideEffectCalls;
}
public void setDefineReplacements(Map<String, Object> defineReplacements) {
this.defineReplacements = defineReplacements;
}
public void setTweakReplacements(Map<String, Object> tweakReplacements) {
this.tweakReplacements = tweakReplacements;
}
public void setMoveFunctionDeclarations(boolean moveFunctionDeclarations) {
this.moveFunctionDeclarations = moveFunctionDeclarations;
}
public void setInstrumentationTemplate(String instrumentationTemplate) {
this.instrumentationTemplate = instrumentationTemplate;
}
public void setRecordFunctionInformation(boolean recordFunctionInformation) {
this.recordFunctionInformation = recordFunctionInformation;
}
public void setCssRenamingMap(CssRenamingMap cssRenamingMap) {
this.cssRenamingMap = cssRenamingMap;
}
public void setReplaceStringsFunctionDescriptions(List<String> replaceStringsFunctionDescriptions) {
this.replaceStringsFunctionDescriptions = replaceStringsFunctionDescriptions;
}
public void setReplaceStringsPlaceholderToken(String replaceStringsPlaceholderToken) {
this.replaceStringsPlaceholderToken = replaceStringsPlaceholderToken;
}
public void setReplaceStringsReservedStrings(Set<String> replaceStringsReservedStrings) {
this.replaceStringsReservedStrings = replaceStringsReservedStrings;
}
public void setPrettyPrint(boolean prettyPrint) {
this.prettyPrint = prettyPrint;
}
public void setLineBreak(boolean lineBreak) {
this.lineBreak = lineBreak;
}
public void setPreferLineBreakAtEndOfFile(boolean lineBreakAtEnd) {
this.preferLineBreakAtEndOfFile = lineBreakAtEnd;
}
public void setPrintInputDelimiter(boolean printInputDelimiter) {
this.printInputDelimiter = printInputDelimiter;
}
public void setInputDelimiter(String inputDelimiter) {
this.inputDelimiter = inputDelimiter;
}
public void setTracer(TracerMode tracer) {
this.tracer = tracer;
}
public void setErrorFormat(ErrorFormat errorFormat) {
this.errorFormat = errorFormat;
}
public void setWarningsGuard(ComposeWarningsGuard warningsGuard) {
this.warningsGuard = warningsGuard;
}
public void setLineLengthThreshold(int lineLengthThreshold) {
this.lineLengthThreshold = lineLengthThreshold;
}
public void setExternExports(boolean externExports) {
this.externExports = externExports;
}
public void setExternExportsPath(String externExportsPath) {
this.externExportsPath = externExportsPath;
}
public void setSourceMapOutputPath(String sourceMapOutput
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>Path) {
this.sourceMapOutputPath = sourceMapOutputPath;
}
public void setSourceMapDetailLevel(SourceMap.DetailLevel sourceMapDetailLevel) {
this.sourceMapDetailLevel = sourceMapDetailLevel;
}
public void setSourceMapFormat(SourceMap.Format sourceMapFormat) {
this.sourceMapFormat = sourceMapFormat;
}
public void setSourceMapLocationMappings(List<SourceMap.LocationMapping> sourceMapLocationMappings) {
this.sourceMapLocationMappings = sourceMapLocationMappings;
}
/**
* Activates transformation of AMD to CommonJS modules.
*/
public void setTransformAMDToCJSModules(boolean transformAMDToCJSModules) {
this.transformAMDToCJSModules = transformAMDToCJSModules;
}
/**
* Rewrites CommonJS modules so that modules can be concatenated together,
* by renaming all globals to avoid conflicting with other modules.
*/
public void setProcessCommonJSModules(boolean processCommonJSModules) {
this.processCommonJSModules = processCommonJSModules;
}
/**
* Sets a path prefix for CommonJS modules.
*/
public void setCommonJSModulePathPrefix(String commonJSModulePathPrefix) {
this.commonJSModulePathPrefix = commonJSModulePathPrefix;
}
//////////////////////////////////////////////////////////////////////////////
// Enums
/** When to do the extra sanity checks */
public static enum LanguageMode {
/**
* Traditional JavaScript
*/
ECMASCRIPT3,
/**
* Shiny new JavaScript
*/
ECMASCRIPT5,
/**
* Nitpicky, shiny new JavaScript
*/
ECMASCRIPT5_STRICT,
}
/** When to do the extra sanity checks */
static enum DevMode {
/**
* Don't do any extra sanity checks.
*/
OFF,
/**
* After the initial parse
*/
START,
/**
* At the start and at the end of all optimizations.
*/
START_AND_END,
/**
* After every pass
*/
EVERY_PASS
}
public static enum TracerMode {
ALL, // Collect all timing and size metrics.
RAW_SIZE, // Collect all timing and size metrics, except gzipped size.
TIMING_ONLY, // Collect timing metrics only.
OFF; // Collect no timing and size metrics.
boolean isOn() {
return this != OFF;
}
}
public static enum TweakProcessing {
OFF, // Do not run the ProcessTweaks pass.
CHECK, // Run the pass, but do not strip out the calls.
STRIP; // Strip out all calls to goog.tweak.*.
public boolean isOn() {
return this != OFF;
}
public boolean shouldStrip() {
return this == STRIP;
}
}
/**
* A Role Specific Interface for JS Compiler that represents a data holder
* object which is used to store goog.scope alias code changes to code made
* during a compile. There is no guarantee that individual alias changes are
* invoked in the order they occur during compilation, so implementations
* should not assume any relevance to the order changes arrive.
* <p>
* Calls to the mutators are expected to resolve very quickly, so
* implementations should not perform expensive operations in the mutator
* methods.
*
* @author tylerg@google.com (Tyler Goodwin)
*/
public interface AliasTransformationHandler {
/**
* Builds an AliasTransformation implementation and
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.graph;
import java.util.List;
/**
* A generic directed graph.
*
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public abstract class DiGraph<N, E> extends Graph<N, E> {
/**
* Gets an immutable iterable over all the nodes in the graph.
*/
public abstract Iterable<DiGraphNode<N, E>> getDirectedGraphNodes();
/**
* Gets an immutable list of out edges of the given node.
*/
public abstract List<DiGraphEdge<N, E>> getOutEdges(N nodeValue);
/**
* Gets an immutable list of in edges of the given node.
*/
public abstract List<DiGraphEdge<N, E>> getInEdges(N nodeValue);
public abstract List<DiGraphNode<N, E>> getDirectedPredNodes(
DiGraphNode<N, E> n);
public abstract List<DiGraphNode<N, E>> getDirectedSuccNodes(
DiGraphNode<N, E> n);
public abstract List<DiGraphNode<N, E>>
getDirectedPredNodes(N nodeValue);
public abstract List<DiGraphNode<N, E>>
getDirectedSuccNodes(N nodeValue);
public abstract DiGraphNode<N, E> createDirectedGraphNode(N nodeValue);
public abstract DiGraphNode<N, E> getDirectedGraphNode(N nodeValue);
public abstract List<DiGraphEdge<N, E>>
getDirectedGraphEdges(N n1, N n2);
/**
* Disconnects all edges from n1 to n2.
*
* @param n1 Source node.
* @param n2 Destination node.
*/
public abstract void disconnectInDirection(N n1, N n2);
/**
* Checks whether two nodes in the graph are connected via a directed edge.
*
* @param n1 Node 1.
* @param n2 Node 2.
* @return <code>true</code> if the graph contains edge from n1 to n2.
*/
public abstract boolean isConnectedInDirection(N n1, N n2);
/**
* Checks whether two nodes in the graph are connected via a directed edge
* with the given value.
*
* @param n1 Node 1.
* @param edgeValue edge value tag
* @param n2 Node 2.
* @return <code>true</code> if the edge exists.
*/
public abstract boolean isConnectedInDirection(N n1, E edgeValue, N n2);
@Override
public boolean isConnected(N n1, N n2)
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> {
return isConnectedInDirection(n1, n2) || isConnectedInDirection(n2, n1);
}
@Override
public boolean isConnected(N n1, E e, N n2) {
return isConnectedInDirection(n1, e, n2) ||
isConnectedInDirection(n2, e, n1);
}
/**
* A generic directed graph node.
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public static interface DiGraphNode<N, E> extends GraphNode<N, E> {
public List<DiGraphEdge<N, E>> getOutEdges();
public List<DiGraphEdge<N, E>> getInEdges();
}
/**
* A generic directed graph edge.
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public static interface DiGraphEdge<N, E> extends GraphEdge<N, E> {
public DiGraphNode<N, E> getSource();
public DiGraphNode<N, E> getDestination();
public void setSource(DiGraphNode<N, E> node);
public void setDestination(DiGraphNode<N, E> node);
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.javascript.jscomp.CheckLevel;
import java.io.Serializable;
import java.util.*;
import java.util.Map;
import java.util.TreeSet;
/**
* WarningsGuard that represents just a chain of other guards. For example we
* could have following chain
* 1) all warnings outside of /foo/ should be suppressed
* 2) errors with key JSC_BAR should be marked as warning
* 3) the rest should be reported as error
*
* This class is designed for such behavior.
*
* @author anatol@google.com (Anatol Pomazau)
*/
public class ComposeWarningsGuard extends WarningsGuard {
private static final long serialVersionUID = 1L;
// The order that the guards were added in.
private final Map<WarningsGuard, Integer> orderOfAddition = Maps.newHashMap();
private int numberOfAdds = 0;
private final Comparator<WarningsGuard> guardComparator =
new GuardComparator(orderOfAddition);
private boolean demoteErrors = false;
private static class GuardComparator
implements Comparator<WarningsGuard>, Serializable {
private static final long serialVersionUID = 1L;
private final Map<WarningsGuard, Integer> orderOfAddition;
private GuardComparator(Map<WarningsGuard, Integer> orderOfAddition) {
this.orderOfAddition = orderOfAddition;
}
@Override
public int compare(WarningsGuard a, WarningsGuard b) {
int priorityDiff = a.getPriority() - b.getPriority();
if (priorityDiff != 0) {
return priorityDiff;
}
// If the warnings guards have the same priority, the one that
// was added last wins.
return orderOfAddition.get(b).intValue() -
orderOfAddition.get(a).intValue();
}
}
// The order that the guards are applied in.
private final TreeSet<WarningsGuard> guards =
new TreeSet<WarningsGuard>(guardComparator);
public ComposeWarningsGuard(List<WarningsGuard> guards) {
addGuards(guards);
}
public ComposeWarningsGuard(WarningsGuard... guards) {
this(Lists.newArrayList(guards));
}
void addGuard(WarningsGuard guard) {
if (guard instanceof ComposeWarningsGuard) {
ComposeWarningsGuard composeGuard = (ComposeWarningsGuard) guard;
if (composeGuard.demoteErrors) {
this.demoteErrors = composeGuard.demoteErrors;
}
// Reverse the guards, so that they have the same order in the result.
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> addGuards(Lists.newArrayList(composeGuard.guards.descendingSet()));
} else {
numberOfAdds++;
orderOfAddition.put(guard, numberOfAdds);
guards.remove(guard);
guards.add(guard);
}
}
private void addGuards(Iterable<WarningsGuard> guards) {
for (WarningsGuard guard : guards) {
addGuard(guard);
}
}
@Override
public CheckLevel level(JSError error) {
for (WarningsGuard guard : guards) {
CheckLevel newLevel = guard.level(error);
if (newLevel != null) {
if (demoteErrors && newLevel == CheckLevel.ERROR) {
return CheckLevel.WARNING;
}
return newLevel;
}
}
return null;
}
@Override
public boolean disables(DiagnosticGroup group) {
nextSingleton:
for (DiagnosticType type : group.getTypes()) {
DiagnosticGroup singleton = DiagnosticGroup.forType(type);
for (WarningsGuard guard : guards) {
if (guard.disables(singleton)) {
continue nextSingleton;
} else if (guard.enables(singleton)) {
return false;
}
}
return false;
}
return true;
}
/**
* Determines whether this guard will "elevate" the status of any disabled
* diagnostic type in the group to a warning or an error.
*/
@Override
public boolean enables(DiagnosticGroup group) {
for (WarningsGuard guard : guards) {
if (guard.enables(group)) {
return true;
} else if (guard.disables(group)) {
return false;
}
}
return false;
}
List<WarningsGuard> getGuards() {
return Collections.unmodifiableList(Lists.newArrayList(guards));
}
/**
* Make a warnings guard that's the same as this one but with
* all escalating guards turned down.
*/
ComposeWarningsGuard makeEmergencyFailSafeGuard() {
ComposeWarningsGuard safeGuard = new ComposeWarningsGuard();
safeGuard.demoteErrors = true;
for (WarningsGuard guard : guards.descendingSet()) {
safeGuard.addGuard(guard);
}
return safeGuard;
}
@Override
public String toString() {
return Joiner.on(", ").join(guards);
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.javascript.jscomp.CodeChangeHandler.RecentChange;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.type.ReverseAbstractInterpreter;
import com.google.javascript.jscomp.type.SemanticReverseAbstractInterpreter;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.testing.BaseJSTypeTestCase;
import junit.framework.TestCase;
import java.io.IOException;
import java.util.List;
/**
* <p>Base class for testing JS compiler classes that change
* the node tree of a compiled JS input.</p>
*
* <p>Pulls in shared functionality from different test cases. Also supports
* node tree comparison for input and output (instead of string comparison),
* which makes it easier to write tests b/c you don't have to get the syntax
* exactly correct to the spacing.</p>
*
*/
public abstract class CompilerTestCase extends TestCase {
/** Externs for the test */
private final List<SourceFile> externsInputs;
/** Whether to compare input and output as trees instead of strings */
private final boolean compareAsTree;
/** Whether to parse type info from JSDoc comments */
protected boolean parseTypeInfo;
/** Whether we check warnings without source information. */
private boolean allowSourcelessWarnings = false;
/** True iff closure pass runs before pass being tested. */
private boolean closurePassEnabled = false;
/** True iff type checking pass runs before pass being tested. */
private boolean typeCheckEnabled = false;
/** Error level reported by type checker. */
private CheckLevel typeCheckLevel;
/** Whether the Normalize pass runs before pass being tested. */
private boolean normalizeEnabled = false;
/** Whether the expected JS strings should be normalized. */
private boolean normalizeExpected = false;
/** Whether to check that all line number information is preserved. */
private boolean checkLineNumbers = true;
/**
* An expected symbol table error. Only useful for testing the
* symbol table error-handling.
*/
private DiagnosticType expectedSymbolTableError = null;
/**
* Whether the MarkNoSideEffectsCalls pass runs before the pass being tested
*/
private boolean markNoSideEffects = false;
/** The most recently used Compiler instance. */
private Compiler lastCompiler;
/**
* Whether to acceptES5 source.
*/
private boolean acceptES5 = true;
/**
* Whether externs changes should
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
* @param warning Expected warning, or null if no warning is expected
* @param description The content of the error expected
*/
public void test(String js, String expected, DiagnosticType error,
DiagnosticType warning, String description) {
test(externsInputs, js, expected, error, warning, description);
}
/**
* Verifies that the compiler pass's JS output matches the expected output
* and (optionally) that an expected warning is issued. Or, if an error is
* expected, this method just verifies that the error is encountered.
*
* @param js Input
* @param expected Expected output, or null if an error is expected
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
*/
public void test(String js, String expected,
DiagnosticType error, DiagnosticType warning) {
test(externsInputs, js, expected, error, warning, null);
}
/**
* Verifies that the compiler pass's JS output matches the expected output
* and (optionally) that an expected warning is issued. Or, if an error is
* expected, this method just verifies that the error is encountered.
*
* @param externs Externs input
* @param js Input
* @param expected Expected output, or null if an error is expected
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
*/
public void test(String externs, String js, String expected,
DiagnosticType error, DiagnosticType warning) {
test(externs, js, expected, error, warning, null);
}
/**
* Verifies that the compiler pass's JS output matches the expected output
* and (optionally) that an expected warning is issued. Or, if an error is
* expected, this method just verifies that the error is encountered.
*
* @param externs Externs input
* @param js Input
* @param expected Expected output, or null if an error is expected
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
* @param description The description of the expected warning,
* or null if no warning is expected or if the warning's description
* should not be examined
*/
public void test(String externs, String js, String expected,
DiagnosticType error, DiagnosticType warning,
String description) {
List<SourceFile> externsInputs = ImmutableList.of(
SourceFile.fromCode("externs", externs));
test(externsInputs, js, expected, error, warning, description);
}
/**
* Verifies that the compiler pass's JS output matches the expected output
* and (optionally) that an expected warning is issued. Or, if an error is
* expected, this method just verifies that the error is encountered.
*
* @param externs Externs inputs
* @param js Input
* @param expected Expected output, or null if an error is expected
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
* @param description The description of the expected warning,
* or null if no warning is expected or if the warning's description
* should not be examined
*/
public void test(List<SourceFile> externs, String js, String expected,
DiagnosticType error,
Diagnostic
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>Type warning, String description) {
Compiler compiler = createCompiler();
lastCompiler = compiler;
CompilerOptions options = getOptions();
if (this.acceptES5) {
options.setLanguageIn(LanguageMode.ECMASCRIPT5);
}
// Note that in this context, turning on the checkTypes option won't
// actually cause the type check to run.
options.checkTypes = parseTypeInfo;
compiler.init(externs, ImmutableList.of(
SourceFile.fromCode(filename, js)), options);
BaseJSTypeTestCase.addNativeProperties(compiler.getTypeRegistry());
test(compiler, new String[] { expected }, error, warning, description);
}
/**
* Verifies that the compiler pass's JS output matches the expected output.
*
* @param js Inputs
* @param expected Expected JS output
*/
public void test(String[] js, String[] expected) {
test(js, expected, null);
}
/**
* Verifies that the compiler pass's JS output matches the expected output,
* or that an expected error is encountered.
*
* @param js Inputs
* @param expected Expected JS output
* @param error Expected error, or null if no error is expected
*/
public void test(String[] js, String[] expected, DiagnosticType error) {
test(js, expected, error, null);
}
/**
* Verifies that the compiler pass's JS output matches the expected output
* and (optionally) that an expected warning is issued. Or, if an error is
* expected, this method just verifies that the error is encountered.
*
* @param js Inputs
* @param expected Expected JS output
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
*/
public void test(String[] js, String[] expected, DiagnosticType error,
DiagnosticType warning) {
test(js, expected, error, warning, null);
}
/**
* Verifies that the compiler pass's JS output matches the expected output
* and (optionally) that an expected warning is issued. Or, if an error is
* expected, this method just verifies that the error is encountered.
*
* @param js Inputs
* @param expected Expected JS output
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
* @param description The description of the expected warning,
* or null if no warning is expected or if the warning's description
* should not be examined
*/
public void test(String[] js, String[] expected, DiagnosticType error,
DiagnosticType warning, String description) {
Compiler compiler = createCompiler();
lastCompiler = compiler;
List<SourceFile> inputs = Lists.newArrayList();
for (int i = 0; i < js.length; i++) {
inputs.add(SourceFile.fromCode("input" + i, js[i]));
}
compiler.init(externsInputs, inputs, getOptions());
test(compiler, expected, error, warning, description);
}
/**
* Verifies that the compiler pass's JS output matches the expected output.
*
* @param modules Module inputs
* @param expected Expected JS outputs (one per module)
*/
public void test(JSModule[] modules, String[] expected) {
test(modules, expected, null);
}
/**
* Verifies that the compiler pass's JS output matches the expected output,
*
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
* Verifies that the compiler pass's JS output is the same as its input
* and (optionally) that an expected warning and description is issued.
*
* @param externs Externs input
* @param js Input and output
* @param warning Expected warning, or null if no warning is expected
* @param description The description of the expected warning,
* or null if no warning is expected or if the warning's description
* should not be examined
*/
public void testSame(String externs, String js, DiagnosticType warning,
String description) {
List<SourceFile> externsInputs = ImmutableList.of(
SourceFile.fromCode("externs", externs));
test(externsInputs, js, js, null, warning, description);
}
/**
* Verifies that the compiler pass's JS output is the same as its input.
*
* @param js Inputs and outputs
*/
public void testSame(String[] js) {
test(js, js);
}
/**
* Verifies that the compiler pass's JS output is the same as its input,
* and emits the given error.
*
* @param js Inputs and outputs
* @param error Expected error, or null if no error is expected
*/
public void testSame(String[] js, DiagnosticType error) {
test(js, js, error);
}
/**
* Verifies that the compiler pass's JS output is the same as its input,
* and emits the given error and warning.
*
* @param js Inputs and outputs
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
*/
public void testSame(
String[] js, DiagnosticType error, DiagnosticType warning) {
test(js, js, error, warning);
}
/**
* Verifies that the compiler pass's JS output is the same as the input.
*
* @param modules Module inputs
*/
public void testSame(JSModule[] modules) {
testSame(modules, null);
}
/**
* Verifies that the compiler pass's JS output is the same as the input.
*
* @param modules Module inputs
* @param warning A warning, or null for no expected warning.
*/
public void testSame(JSModule[] modules, DiagnosticType warning) {
try {
String[] expected = new String[modules.length];
for (int i = 0; i < modules.length; i++) {
expected[i] = "";
for (CompilerInput input : modules[i].getInputs()) {
expected[i] += input.getSourceFile().getCode();
}
}
test(modules, expected, null, warning);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Verifies that the compiler pass's JS output matches the expected output
* and (optionally) that an expected warning is issued. Or, if an error is
* expected, this method just verifies that the error is encountered.
*
* @param compiler A compiler that has been initialized via
* {@link Compiler#init}
* @param expected Expected output, or null if an error is expected
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
*/
protected void test(Compiler compiler, String[] expected,
DiagnosticType error, DiagnosticType warning) {
test(compiler, expected, error, warning,
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> null);
}
/**
* Verifies that the compiler pass's JS output matches the expected output
* and (optionally) that an expected warning is issued. Or, if an error is
* expected, this method just verifies that the error is encountered.
*
* @param compiler A compiler that has been initialized via
* {@link Compiler#init}
* @param expected Expected output, or null if an error is expected
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
* @param description The description of the expected warning,
* or null if no warning is expected or if the warning's description
* should not be examined
*/
private void test(Compiler compiler, String[] expected,
DiagnosticType error, DiagnosticType warning,
String description) {
RecentChange recentChange = new RecentChange();
compiler.addChangeHandler(recentChange);
Node root = compiler.parseInputs();
assertTrue("Unexpected parse error(s): " +
Joiner.on("\n").join(compiler.getErrors()), root != null);
if (astValidationEnabled) {
(new AstValidator()).validateRoot(root);
}
Node externsRoot = root.getFirstChild();
Node mainRoot = root.getLastChild();
// Save the tree for later comparison.
Node rootClone = root.cloneTree();
Node externsRootClone = rootClone.getFirstChild();
Node mainRootClone = rootClone.getLastChild();
int numRepetitions = getNumRepetitions();
ErrorManager[] errorManagers = new ErrorManager[numRepetitions];
int aggregateWarningCount = 0;
List<JSError> aggregateWarnings = Lists.newArrayList();
boolean hasCodeChanged = false;
assertFalse("Code should not change before processing",
recentChange.hasCodeChanged());
for (int i = 0; i < numRepetitions; ++i) {
if (compiler.getErrorCount() == 0) {
errorManagers[i] = new BlackHoleErrorManager(compiler);
// Only run process closure primitives once, if asked.
if (closurePassEnabled && i == 0) {
recentChange.reset();
new ProcessClosurePrimitives(compiler, null, CheckLevel.ERROR)
.process(null, mainRoot);
hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged();
}
// Only run the type checking pass once, if asked.
// Running it twice can cause unpredictable behavior because duplicate
// objects for the same type are created, and the type system
// uses reference equality to compare many types.
if (typeCheckEnabled && i == 0) {
TypeCheck check = createTypeCheck(compiler, typeCheckLevel);
check.processForTesting(externsRoot, mainRoot);
}
// Only run the normalize pass once, if asked.
if (normalizeEnabled && i == 0) {
normalizeActualCode(compiler, externsRoot, mainRoot);
}
if (markNoSideEffects && i == 0) {
MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler);
mark.process(externsRoot, mainRoot);
}
recentChange.reset();
getProcessor(compiler).process(externsRoot, mainRoot);
if (astValidationEnabled) {
(new AstValidator()).validateRoot(root);
}
if (checkLineNumbers) {
(new LineNumberCheck(compiler)).process(externsRoot, mainRoot);
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> +
"\nResult: " + compiler.toSource(mainRoot) +
"\n" + explanation, explanation);
} else if (expected != null) {
assertEquals(
Joiner.on("").join(expected), compiler.toSource(mainRoot));
}
// Verify normalization is not invalidated.
Node normalizeCheckRootClone = root.cloneTree();
Node normalizeCheckExternsRootClone = root.getFirstChild();
Node normalizeCheckMainRootClone = root.getLastChild();
new PrepareAst(compiler).process(
normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
String explanation =
normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
assertNull("Node structure normalization invalidated.\nExpected: " +
compiler.toSource(normalizeCheckMainRootClone) +
"\nResult: " + compiler.toSource(mainRoot) +
"\n" + explanation, explanation);
// TODO(johnlenz): enable this for most test cases.
// Currently, this invalidates test for while-loops, for-loop
// initializers, and other naming. However, a set of code
// (FoldConstants, etc) runs before the Normalize pass, so this can't be
// force on everywhere.
if (normalizeEnabled) {
new Normalize(compiler, true).process(
normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
assertNull("Normalization invalidated.\nExpected: " +
compiler.toSource(normalizeCheckMainRootClone) +
"\nResult: " + compiler.toSource(mainRoot) +
"\n" + explanation, explanation);
}
} else {
String errors = "";
for (JSError actualError : compiler.getErrors()) {
errors += actualError.description + "\n";
}
assertEquals("There should be one error. " + errors,
1, compiler.getErrorCount());
assertEquals(errors, error, compiler.getErrors()[0].getType());
if (warning != null) {
String warnings = "";
for (JSError actualError : compiler.getWarnings()) {
warnings += actualError.description + "\n";
}
assertEquals("There should be one warning. " + warnings,
1, compiler.getWarningCount());
assertEquals(warnings, warning, compiler.getWarnings()[0].getType());
}
}
}
private void normalizeActualCode(
Compiler compiler, Node externsRoot, Node mainRoot) {
Normalize normalize = new Normalize(compiler, false);
normalize.process(externsRoot, mainRoot);
}
/**
* Parses expected JS inputs and returns the root of the parse tree.
*/
protected Node parseExpectedJs(String[] expected) {
Compiler compiler = createCompiler();
List<SourceFile> inputs = Lists.newArrayList();
for (int i = 0; i < expected.length; i++) {
inputs.add(SourceFile.fromCode("expected" + i, expected[i]));
}
compiler.init(externsInputs, inputs, getOptions());
Node root = compiler.parseInputs();
assertTrue("Unexpected parse error(s): " +
Joiner.on("\n").join(compiler.getErrors()), root != null);
Node externsRoot = root.getFirstChild();
Node mainRoot = externsRoot.getNext();
// Only run the normalize pass, if asked.
if (normalizeEnabled && normalizeExpected && !compiler.hasErrors()) {
Normalize normalize = new Normalize
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>(compiler, false);
normalize.process(externsRoot, mainRoot);
}
return mainRoot;
}
protected Node parseExpectedJs(String expected) {
return parseExpectedJs(new String[] {expected});
}
/**
* Generates a list of modules from a list of inputs, such that each module
* depends on the module before it.
*/
static JSModule[] createModuleChain(String... inputs) {
JSModule[] modules = createModules(inputs);
for (int i = 1; i < modules.length; i++) {
modules[i].addDependency(modules[i - 1]);
}
return modules;
}
/**
* Generates a list of modules from a list of inputs, such that each module
* depends on the first module.
*/
static JSModule[] createModuleStar(String... inputs) {
JSModule[] modules = createModules(inputs);
for (int i = 1; i < modules.length; i++) {
modules[i].addDependency(modules[0]);
}
return modules;
}
/**
* Generates a list of modules from a list of inputs, such that modules
* form a bush formation. In a bush formation, module 2 depends
* on module 1, and all other modules depend on module 2.
*/
static JSModule[] createModuleBush(String ... inputs) {
Preconditions.checkState(inputs.length > 2);
JSModule[] modules = createModules(inputs);
for (int i = 1; i < modules.length; i++) {
modules[i].addDependency(modules[i == 1 ? 0 : 1]);
}
return modules;
}
/**
* Generates a list of modules from a list of inputs, such that modules
* form a tree formation. In a tree formation, module N depends on
* module `floor(N/2)`, So the modules form a balanced binary tree.
*/
static JSModule[] createModuleTree(String ... inputs) {
JSModule[] modules = createModules(inputs);
for (int i = 1; i < modules.length; i++) {
modules[i].addDependency(modules[(i - 1) / 2]);
}
return modules;
}
/**
* Generates a list of modules from a list of inputs. Does not generate any
* dependencies between the modules.
*/
static JSModule[] createModules(String... inputs) {
JSModule[] modules = new JSModule[inputs.length];
for (int i = 0; i < inputs.length; i++) {
JSModule module = modules[i] = new JSModule("m" + i);
module.add(SourceFile.fromCode("i" + i, inputs[i]));
}
return modules;
}
private static class BlackHoleErrorManager extends BasicErrorManager {
private BlackHoleErrorManager(Compiler compiler) {
compiler.setErrorManager(this);
}
@Override
public void println(CheckLevel level, JSError error) {}
@Override
public void printSummary() {}
}
Compiler createCompiler() {
Compiler compiler = new Compiler();
return compiler;
}
protected void setExpectedSymbolTableError(DiagnosticType type) {
this.expectedSymbolTableError = type;
}
/** Finds the first matching qualified name node in post-traversal order. */
protected final Node findQualifiedNameNode(final String name, Node root) {
final List<Node> matches = Lists.
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2007 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.CheckLevel;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
/**
* <p>A basic error manager that sorts all errors and warnings reported to it to
* generate a sorted report when the {@link #generateReport()} method
* is called.</p>
*
* <p>This error manager does not produce any output, but subclasses can
* override the {@link #println(CheckLevel, JSError)} method to generate custom
* output.</p>
*
*/
public abstract class BasicErrorManager implements ErrorManager {
private final SortedSet<ErrorWithLevel> messages =
Sets.newTreeSet(new LeveledJSErrorComparator());
private int errorCount = 0;
private int warningCount = 0;
private double typedPercent = 0.0;
@Override
public void report(CheckLevel level, JSError error) {
if (messages.add(new ErrorWithLevel(error, level))) {
if (level == CheckLevel.ERROR) {
errorCount++;
} else if (level == CheckLevel.WARNING) {
warningCount++;
}
}
}
@Override
public void generateReport() {
for (ErrorWithLevel message : messages) {
println(message.level, message.error);
}
printSummary();
}
/**
* Print a message with a trailing new line. This method is called by the
* {@link #generateReport()} method when generating messages.
*/
public abstract void println(CheckLevel level, JSError error);
/**
* Print the summary of the compilation - number of errors and warnings.
*/
protected abstract void printSummary();
@Override
public int getErrorCount() {
return errorCount;
}
@Override
public int getWarningCount() {
return warningCount;
}
@Override
public JSError[] getErrors() {
return toArray(CheckLevel.ERROR);
}
@Override
public JSError[] getWarnings() {
return toArray(CheckLevel.WARNING);
}
@Override
public void setTypedPercent(double typedPercent) {
this.typedPercent = typedPercent;
}
@Override
public double getTypedPercent() {
return typedPercent;
}
private JSError[] toArray(CheckLevel level) {
List<JSError> errors = new ArrayList<JSError>(messages.size());
for (ErrorWithLevel p : messages) {
if (p.level == level) {
errors.add(p.error);
}
}
return errors.toArray(new JSError[errors.size()]);
}
/**
*
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Supplier;
import com.google.javascript.jscomp.ReferenceCollectingCallback.ReferenceCollection;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.jscomp.parsing.Config;
import com.google.javascript.jscomp.type.ReverseAbstractInterpreter;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.head.ErrorReporter;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import java.util.List;
import java.util.Map;
/**
* An abstract compiler, to help remove the circular dependency of
* passes on JSCompiler.
*
* This is an abstract class, so that we can make the methods package-private.
*
* @author nicksantos@google.com (Nick Santos)
*/
public abstract class AbstractCompiler implements SourceExcerptProvider {
static final DiagnosticType READ_ERROR = DiagnosticType.error(
"JSC_READ_ERROR", "Cannot read: {0}");
private LifeCycleStage stage = LifeCycleStage.RAW;
// TODO(nicksantos): Decide if all of these are really necessary.
// Many of them are just accessors that should be passed to the
// CompilerPass's constructor.
/**
* Looks up an input (possibly an externs input) by input id.
* May return null.
*/
public abstract CompilerInput getInput(InputId inputId);
/**
* Looks up a source file by name. May return null.
*/
abstract SourceFile getSourceFileByName(String sourceName);
/**
* Creates a new externs file.
* @param name A name for the new externs file.
* @throws IllegalArgumentException If the name of the externs file conflicts
* with a pre-existing externs file.
*/
abstract CompilerInput newExternInput(String name);
/**
* Gets the module graph. May return null if there aren't at least two
* modules.
*/
abstract JSModuleGraph getModuleGraph();
/**
* Gets the inputs in the order in which they are being processed.
* Only for use by {@code AbstractCompilerRunner}.
*/
abstract List<CompilerInput> getInputsInOrder();
/**
* Gets a central registry of type information from the compiled JS.
*/
public abstract JSTypeRegistry getTypeRegistry();
/**
* Gets a memoized scope creator with type information.
*/
abstract ScopeCreator getTypedScopeCreator();
/**
* Gets the top scope.
*/
public abstract Scope getTopScope();
/**
* Report an error or warning.
*/
public abstract void report(JSError
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
}
private final SourceMapGenerator generator;
private List<LocationMapping> prefixMappings = Collections.emptyList();
private final Map<String, String> sourceLocationFixupCache =
Maps.newHashMap();
private SourceMap(SourceMapGenerator generator) {
this.generator = generator;
}
public void addMapping(
Node node,
FilePosition outputStartPosition,
FilePosition outputEndPosition) {
String sourceFile = node.getSourceFileName();
// If the node does not have an associated source file or
// its line number is -1, then the node does not have sufficient
// information for a mapping to be useful.
if (sourceFile == null || node.getLineno() < 0) {
return;
}
sourceFile = fixupSourceLocation(sourceFile);
String originalName = (String) node.getProp(Node.ORIGINALNAME_PROP);
// Strangely, Rhino source lines are one based but columns are
// zero based.
// We don't change this for the v1 or v2 source maps but for
// v3 we make them both 0 based.
int lineBaseOffset = 1;
if (generator instanceof SourceMapGeneratorV1
|| generator instanceof SourceMapGeneratorV2) {
lineBaseOffset = 0;
}
generator.addMapping(
sourceFile, originalName,
new FilePosition(node.getLineno() - lineBaseOffset, node.getCharno()),
outputStartPosition, outputEndPosition);
}
/**
* @param sourceFile The source file location to fixup.
* @return a remapped source file.
*/
private String fixupSourceLocation(String sourceFile) {
if (prefixMappings.isEmpty()) {
return sourceFile;
}
String fixed = sourceLocationFixupCache.get(sourceFile);
if (fixed != null) {
return fixed;
}
// Replace the first prefix found with its replacement
for (LocationMapping mapping : prefixMappings) {
if (sourceFile.startsWith(mapping.prefix)) {
fixed = mapping.replacement + sourceFile.substring(
mapping.prefix.length());
break;
}
}
// If none of the mappings match then use the original file path.
if (fixed == null) {
fixed = sourceFile;
}
sourceLocationFixupCache.put(sourceFile, fixed);
return fixed;
}
public void appendTo(Appendable out, String name) throws IOException {
generator.appendTo(out, name);
}
public void reset() {
generator.reset();
sourceLocationFixupCache.clear();
}
public void setStartingPosition(int offsetLine, int offsetIndex) {
generator.setStartingPosition(offsetLine, offsetIndex);
}
public void setWrapperPrefix(String prefix) {
generator.setWrapperPrefix(prefix);
}
public void validate(boolean validate) {
generator.validate(validate);
}
/**
* @param sourceMapLocationMappings
*/
public void setPrefixMappings(List<LocationMapping> sourceMapLocationMappings) {
this.prefixMappings = sourceMapLocationMappings;
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.graph;
/**
* A generic node.
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public interface GraphNode<N, E> extends Annotatable {
/**
* Retrieves the node's value.
*
* @return The value.
*/
N getValue();
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.deps.SortedDependencies;
import com.google.javascript.jscomp.deps.SortedDependencies.CircularDependencyException;
import com.google.javascript.jscomp.deps.SortedDependencies.MissingProvideException;
import com.google.javascript.jscomp.graph.LinkedDirectedGraph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* A {@link JSModule} dependency graph that assigns a depth to each module and
* can answer depth-related queries about them. For the purposes of this class,
* a module's depth is defined as the number of hops in the longest path from
* the module to a module with no dependencies.
*
*/
public class JSModuleGraph {
private List<JSModule> modules;
/**
* Lists of modules at each depth. <code>modulesByDepth.get(3)</code> is a
* list of the modules at depth 3, for example.
*/
private List<List<JSModule>> modulesByDepth;
/**
* dependencyMap is a cache of dependencies that makes the dependsOn
* function faster. Each map entry associates a starting
* JSModule with the set of JSModules that are transitively dependent on the
* starting module.
*
* If the cache returns null, then the entry hasn't been filled in for that
* module.
*
* dependencyMap should be filled from leaf to root so that
* getTransitiveDepsDeepestFirst can use its results directly.
*/
private Map<JSModule, Set<JSModule>> dependencyMap = Maps.newHashMap();
/**
* Creates a module graph from a list of modules in dependency order.
*/
public JSModuleGraph(JSModule[] modulesInDepOrder) {
this(ImmutableList.copyOf(modulesInDepOrder));
}
/**
* Creates a module graph from a list of modules in dependency order.
*/
public JSModuleGraph(List<JSModule> modulesInDep
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>Order) {
Preconditions.checkState(
modulesInDepOrder.size() == Sets.newHashSet(modulesInDepOrder).size(),
"Found duplicate modules");
modules = ImmutableList.copyOf(modulesInDepOrder);
modulesByDepth = Lists.newArrayList();
for (JSModule module : modulesInDepOrder) {
int depth = 0;
for (JSModule dep : module.getDependencies()) {
int depDepth = dep.getDepth();
if (depDepth < 0) {
throw new ModuleDependenceException(String.format(
"Modules not in dependency order: %s preceded %s",
module.getName(), dep.getName()),
module, dep);
}
depth = Math.max(depth, depDepth + 1);
}
module.setDepth(depth);
if (depth == modulesByDepth.size()) {
modulesByDepth.add(new ArrayList<JSModule>());
}
modulesByDepth.get(depth).add(module);
}
}
/**
* Gets an iterable over all modules in dependency order.
*/
Iterable<JSModule> getAllModules() {
return modules;
}
/**
* Gets the total number of modules.
*/
int getModuleCount() {
return modules.size();
}
/**
* Gets the root module.
*/
JSModule getRootModule() {
return Iterables.getOnlyElement(modulesByDepth.get(0));
}
/**
* Determines whether this module depends on a given module. Note that a
* module never depends on itself, as that dependency would be cyclic.
*/
public boolean dependsOn(JSModule src, JSModule m) {
Set<JSModule> deps = dependencyMap.get(src);
if (deps == null) {
deps = getTransitiveDepsDeepestFirst(src);
dependencyMap.put(src, deps);
}
return deps.contains(m);
}
/**
* Finds the deepest common dependency of two modules, not including the two
* modules themselves.
*
* @param m1 A module in this graph
* @param m2 A module in this graph
* @return The deepest common dep of {@code m1} and {@code m2}, or null if
* they have no common dependencies
*/
JSModule getDeepestCommonDependency(JSModule m1, JSModule m2) {
int m1Depth = m1.getDepth();
int m2Depth = m2.getDepth();
// According our definition of depth, the result must have a strictly
// smaller depth than either m1 or m2.
for (int depth = Math.min(m1Depth, m2Depth) - 1; depth >= 0; depth--) {
List<JSModule> modulesAtDepth = modulesByDepth.get(depth);
// Look at the modules at this depth in reverse order, so that we use the
// original ordering of the modules to break ties (later meaning deeper).
for (int i = modulesAtDepth.size() - 1; i >= 0; i--) {
JSModule m = modulesAtDepth.get(i);
if (dependsOn(m1, m) && dependsOn(m2, m)) {
return m;
}
}
}
return null;
}
/**
* Finds the deepest common dependency of two modules, including the
* modules themselves.
*
* @param m1 A module in this graph
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> to reflect the new list.
*
* If you need more fine-grained dependency management, you should create your
* own DependencyOptions and call
* {@code manageDependencies(DependencyOptions, List<CompilerInput>)}.
*
* @param entryPoints The entry points into the program.
* Expressed as JS symbols.
* @param inputs The original list of sources. Used to ensure that the sort
* is stable.
* @throws CircularDependencyException if there is a circular dependency
* between the provides and requires.
* @throws MissingProvideException if an entry point was not provided
* by any of the inputs.
* @see DependencyOptions for more info on how this works.
*/
public List<CompilerInput> manageDependencies(
List<String> entryPoints,
List<CompilerInput> inputs)
throws CircularDependencyException, MissingProvideException {
DependencyOptions depOptions = new DependencyOptions();
depOptions.setDependencySorting(true);
depOptions.setDependencyPruning(true);
depOptions.setEntryPoints(entryPoints);
return manageDependencies(depOptions, inputs);
}
/**
* Apply the dependency options to the list of sources, returning a new
* source list re-ordering and dropping files as necessary.
* This module graph will be updated to reflect the new list.
*
* @param inputs The original list of sources. Used to ensure that the sort
* is stable.
* @throws CircularDependencyException if there is a circular dependency
* between the provides and requires.
* @throws MissingProvideException if an entry point was not provided
* by any of the inputs.
* @see DependencyOptions for more info on how this works.
*/
public List<CompilerInput> manageDependencies(
DependencyOptions depOptions,
List<CompilerInput> inputs)
throws CircularDependencyException, MissingProvideException {
SortedDependencies<CompilerInput> sorter =
new SortedDependencies<CompilerInput>(inputs);
Set<CompilerInput> entryPointInputs = Sets.newLinkedHashSet();
if (depOptions.shouldPruneDependencies()) {
if (!depOptions.shouldDropMoochers()) {
entryPointInputs.addAll(sorter.getInputsWithoutProvides());
}
for (String entryPoint : depOptions.getEntryPoints()) {
entryPointInputs.add(sorter.getInputProviding(entryPoint));
}
CompilerInput baseJs = sorter.maybeGetInputProviding("goog");
if (baseJs != null) {
entryPointInputs.add(baseJs);
}
} else {
entryPointInputs.addAll(inputs);
}
// The order of inputs, sorted independently of modules.
List<CompilerInput> absoluteOrder =
sorter.getDependenciesOf(inputs, depOptions.shouldSortDependencies());
// Figure out which sources *must* be in each module.
ListMultimap<JSModule, CompilerInput> entryPointInputsPerModule =
LinkedListMultimap.create();
for (CompilerInput input : entryPointInputs) {
JSModule module = input.getModule();
Preconditions.checkNotNull(module);
entryPointInputsPerModule.put(module, input);
}
// Clear the modules of their inputs. This also nulls out
// the input's reference to its module.
for (JSModule module : getAllModules()) {
module.removeAll();
}
// Figure out which sources *must* be in each module, or in one
// of that module's dependencies.
for (JSModule module : entryPointInputsPerModule.keySet()) {
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> List<CompilerInput> transitiveClosure =
sorter.getDependenciesOf(
entryPointInputsPerModule.get(module),
depOptions.shouldSortDependencies());
for (CompilerInput input : transitiveClosure) {
JSModule oldModule = input.getModule();
if (oldModule == null) {
input.setModule(module);
} else {
input.setModule(null);
input.setModule(
getDeepestCommonDependencyInclusive(oldModule, module));
}
}
}
// All the inputs are pointing to the modules that own them. Yeah!
// Update the modules to reflect this.
for (CompilerInput input : absoluteOrder) {
JSModule module = input.getModule();
if (module != null) {
module.add(input);
}
}
// Now, generate the sorted result.
List<CompilerInput> result = Lists.newArrayList();
for (JSModule module : getAllModules()) {
result.addAll(module.getInputs());
}
return result;
}
LinkedDirectedGraph<JSModule, String> toGraphvizGraph() {
LinkedDirectedGraph<JSModule, String> graphViz =
LinkedDirectedGraph.create();
for (JSModule module : getAllModules()) {
graphViz.createNode(module);
for (JSModule dep : module.getDependencies()) {
graphViz.createNode(dep);
graphViz.connect(module, "->", dep);
}
}
return graphViz;
}
/**
* A module depth comparator that considers a deeper module to be "less than"
* a shallower module. Uses module names to consistently break ties.
*/
private class InverseDepthComparator implements Comparator<JSModule> {
@Override
public int compare(JSModule m1, JSModule m2) {
return depthCompare(m2, m1);
}
}
private int depthCompare(JSModule m1, JSModule m2) {
if (m1 == m2) {
return 0;
}
int d1 = m1.getDepth();
int d2 = m2.getDepth();
return d1 < d2 ? -1 : d2 == d1 ? m1.getName().compareTo(m2.getName()) : 1;
}
/*
* Exception class for declaring when the modules being fed into a
* JSModuleGraph as input aren't in dependence order, and so can't be
* processed for caching of various dependency-related queries.
*/
protected static class ModuleDependenceException
extends IllegalArgumentException {
private static final long serialVersionUID = 1;
private final JSModule module;
private final JSModule dependentModule;
protected ModuleDependenceException(String message,
JSModule module, JSModule dependentModule) {
super(message);
this.module = module;
this.dependentModule = dependentModule;
}
public JSModule getModule() {
return module;
}
public JSModule getDependentModule() {
return dependentModule;
}
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2005 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.deps.DependencyInfo;
import com.google.javascript.jscomp.deps.SortedDependencies;
import com.google.javascript.jscomp.deps.SortedDependencies.CircularDependencyException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* A JavaScript module has a unique name, consists of a list of compiler inputs,
* and can depend on other modules.
*
*/
public class JSModule implements DependencyInfo, Serializable {
private static final long serialVersionUID = 1;
static final DiagnosticType CIRCULAR_DEPENDENCY_ERROR =
DiagnosticType.error("JSC_CIRCULAR_DEP",
"Circular dependency detected: {0}");
/** Module name */
private final String name;
/** Source code inputs */
private final List<CompilerInput> inputs = new ArrayList<CompilerInput>();
/** Modules that this module depends on */
private final List<JSModule> deps = new ArrayList<JSModule>();
private int depth;
/**
* Creates an instance.
*
* @param name A unique name for the module
*/
public JSModule(String name) {
this.name = name;
this.depth = -1;
}
/** Gets the module name. */
@Override
public String getName() {
return name;
}
@Override
public List<String> getProvides() {
return ImmutableList.<String>of(name);
}
@Override
public List<String> getRequires() {
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (JSModule m : deps) {
builder.add(m.getName());
}
return builder.build();
}
@Override
public String getPathRelativeToClosureBase() {
throw new UnsupportedOperationException();
}
/** Adds a source file input to this module. */
public void add(SourceFile file) {
add(new CompilerInput(file));
}
/** Adds a source file input to this module. */
public void addFirst(SourceFile file) {
addFirst(new CompilerInput(file));
}
/** Adds a source code input to this module. */
public void add(CompilerInput input) {
inputs.add(input);
input.setModule(this);
}
/**
* Adds a source code input to this module. Call only if the input might
* already be associated with a module.
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> Otherwise, use
* add(CompilerInput input).
*/
void addAndOverrideModule(CompilerInput input) {
inputs.add(input);
input.overrideModule(this);
}
/** Adds a source code input to this module. */
public void addFirst(CompilerInput input) {
inputs.add(0, input);
input.setModule(this);
}
/** Adds a source code input to this module directly after other. */
public void addAfter(CompilerInput input, CompilerInput other) {
Preconditions.checkState(inputs.contains(other));
inputs.add(inputs.indexOf(other), input);
input.setModule(this);
}
/** Adds a dependency on another module. */
public void addDependency(JSModule dep) {
Preconditions.checkNotNull(dep);
Preconditions.checkState(dep != this);
deps.add(dep);
}
/** Removes an input from this module. */
public void remove(CompilerInput input) {
input.setModule(null);
inputs.remove(input);
}
/** Removes all of the inputs from this module. */
public void removeAll() {
for (CompilerInput input : inputs) {
input.setModule(null);
}
inputs.clear();
}
/**
* Gets the list of modules that this module depends on.
*
* @return A list that may be empty but not null
*/
public List<JSModule> getDependencies() {
return deps;
}
/**
* Gets the names of the modules that this module depends on,
* sorted alphabetically.
*/
List<String> getSortedDependencyNames() {
List<String> names = Lists.newArrayList();
for (JSModule module : getDependencies()) {
names.add(module.getName());
}
Collections.sort(names);
return names;
}
/**
* Returns the transitive closure of dependencies starting from the
* dependencies of this module.
*/
public Set<JSModule> getAllDependencies() {
Set<JSModule> allDeps = Sets.newHashSet(deps);
List<JSModule> workList = Lists.newArrayList(deps);
while (workList.size() > 0) {
JSModule module = workList.remove(workList.size() - 1);
for (JSModule dep : module.getDependencies()) {
if (allDeps.add(dep)) {
workList.add(dep);
}
}
}
return allDeps;
}
/** Returns this module and all of its dependencies in one list. */
public Set<JSModule> getThisAndAllDependencies() {
Set<JSModule> deps = getAllDependencies();
deps.add(this);
return deps;
}
/**
* Gets this module's list of source code inputs.
*
* @return A list that may be empty but not null
*/
public List<CompilerInput> getInputs() {
return inputs;
}
/** Returns the input with the given name or null if none. */
public CompilerInput getByName(String name) {
for (CompilerInput input : inputs) {
if (name.equals(input.getName())) {
return input;
}
}
return null;
}
/**
* Removes any input with the given name. Returns whether any were removed.
*/
public boolean removeByName(String name) {
boolean found = false;
Iterator<CompilerInput> iter = inputs.iterator();
while (iter.hasNext()) {
CompilerInput file =
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> iter.next();
if (name.equals(file.getName())) {
iter.remove();
file.setModule(null);
found = true;
}
}
return found;
}
/** Returns the module name (primarily for debugging). */
@Override
public String toString() {
return name;
}
/**
* Removes any references to nodes of the AST. This method is needed to
* allow the ASTs to be garbage collected if the modules are kept around.
*/
public void clearAsts() {
for (CompilerInput input : inputs) {
input.clearAst();
}
}
/**
* Puts the JS files into a topologically sorted order by their dependencies.
*/
public void sortInputsByDeps(Compiler compiler) {
// Set the compiler, so that we can parse requires/provides and report
// errors properly.
for (CompilerInput input : inputs) {
input.setCompiler(compiler);
}
// Sort the JSModule in this order.
try {
List<CompilerInput> sortedList =
(new SortedDependencies<CompilerInput>(
Collections.<CompilerInput>unmodifiableList(inputs)))
.getSortedList();
inputs.clear();
inputs.addAll(sortedList);
} catch (CircularDependencyException e) {
compiler.report(
JSError.make(CIRCULAR_DEPENDENCY_ERROR, e.getMessage()));
}
}
/**
* Returns the given collection of modules in topological order.
*
* Note that this will return the modules in the same order if they are
* already sorted, and in general, will only change the order as necessary to
* satisfy the ordering dependencies. This can be important for cases where
* the modules do not properly specify all dependencies.
*/
public static JSModule[] sortJsModules(Collection<JSModule> modules)
throws CircularDependencyException {
// Sort the JSModule in this order.
List<JSModule> sortedList = (new SortedDependencies<JSModule>(
Lists.newArrayList(modules))).getSortedList();
return sortedList.toArray(new JSModule[sortedList.size()]);
}
/**
* @param dep the depth to set
*/
public void setDepth(int dep) {
this.depth = dep;
}
/**
* @return the depth
*/
public int getDepth() {
return depth;
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> n, Node parent) {
if (parent != null) {
switch (parent.getType()) {
case Token.DO:
case Token.FOR:
case Token.TRY:
case Token.WHILE:
case Token.WITH:
// NOTE: TRY has up to 3 child blocks:
// TRY
// BLOCK
// BLOCK
// CATCH
// BLOCK
// Note that there is an explicit CATCH token but no explicit
// FINALLY token. For simplicity, we consider each BLOCK
// a separate basic BLOCK.
return true;
case Token.AND:
case Token.HOOK:
case Token.IF:
case Token.OR:
// The first child of a conditional is not a boundary,
// but all the rest of the children are.
return n != parent.getFirstChild();
}
}
return n.isCase();
}
private void addReference(NodeTraversal t, Var v, Reference reference) {
// Create collection if none already
ReferenceCollection referenceInfo = referenceMap.get(v);
if (referenceInfo == null) {
referenceInfo = new ReferenceCollection();
referenceMap.put(v, referenceInfo);
}
// Add this particular reference
referenceInfo.add(reference, t, v);
}
interface ReferenceMap {
ReferenceCollection getReferences(Var var);
}
private static class ReferenceMapWrapper implements ReferenceMap {
private final Map<Var, ReferenceCollection> referenceMap;
public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) {
this.referenceMap = referenceMap;
}
@Override
public ReferenceCollection getReferences(Var var) {
return referenceMap.get(var);
}
}
/**
* Way for callers to add specific behavior during traversal that
* utilizes the built-up reference information.
*/
interface Behavior {
/**
* Called after we finish with a scope.
*/
void afterExitScope(NodeTraversal t, ReferenceMap referenceMap);
}
static Behavior DO_NOTHING_BEHAVIOR = new Behavior() {
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {}
};
/**
* A collection of references. Can be subclassed to apply checks or
* store additional state when adding.
*/
static class ReferenceCollection implements Iterable<Reference> {
List<Reference> references = Lists.newArrayList();
@Override
public Iterator<Reference> iterator() {
return references.iterator();
}
void add(Reference reference, NodeTraversal t, Var v) {
references.add(reference);
}
/**
* Determines if the variable for this reference collection is
* "well-defined." A variable is well-defined if we can prove at
* compile-time that it's assigned a value before it's used.
*
* Notice that if this function returns false, this doesn't imply that the
* variable is used before it's assigned. It just means that we don't
* have enough information to make a definitive judgment.
*/
protected boolean isWellDefined() {
int size = references.size();
if (size == 0) {
return false;
}
// If this is a declaration that does not instantiate the variable,
// it's not well-defined.
Reference init = getInitializingReference();
if (init == null) {
return false;
}
Preconditions.checkState(references.get(0).isDeclaration());
BasicBlock initBlock = init.getBasicBlock();
for (
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>() != null)) {
fileOverviewInfo.setLicense(irNode.getJSDocInfo().getLicense());
}
irNode.setJSDocInfo(fileOverviewInfo);
fileOverviewInfo.setAssociatedNode(irNode);
}
}
private Node transformBlock(AstNode node) {
Node irNode = transform(node);
if (!irNode.isBlock()) {
if (irNode.isEmpty()) {
irNode.setType(Token.BLOCK);
irNode.setWasEmptyNode(true);
} else {
Node newBlock = newNode(Token.BLOCK, irNode);
newBlock.setLineno(irNode.getLineno());
newBlock.setCharno(irNode.getCharno());
maybeSetLengthFrom(newBlock, node);
irNode = newBlock;
}
}
return irNode;
}
/**
* Check to see if the given block comment looks like it should be JSDoc.
*/
private void handleBlockComment(Comment comment) {
String value = comment.getValue();
if (value.indexOf("/* @") != -1 ||
value.indexOf("\n * @") != -1) {
errorReporter.warning(
SUSPICIOUS_COMMENT_WARNING,
sourceName,
comment.getLineno(), "", 0);
}
}
/**
* @return true if the jsDocParser represents a fileoverview.
*/
private boolean handlePossibleFileOverviewJsDoc(
JsDocInfoParser jsDocParser) {
if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) {
fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo();
return true;
}
return false;
}
private void handlePossibleFileOverviewJsDoc(Comment comment, Node irNode) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode);
parsedComments.add(comment);
handlePossibleFileOverviewJsDoc(jsDocParser);
}
private JSDocInfo handleJsDoc(AstNode node, Node irNode) {
Comment comment = node.getJsDocNode();
if (comment != null) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode);
parsedComments.add(comment);
if (!handlePossibleFileOverviewJsDoc(jsDocParser)) {
return jsDocParser.retrieveAndResetParsedJSDocInfo();
}
}
return null;
}
private Node transform(AstNode node) {
Node irNode = justTransform(node);
JSDocInfo jsDocInfo = handleJsDoc(node, irNode);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, node);
return irNode;
}
private Node transformNameAsString(Name node) {
Node irNode = transformDispatcher.processName(node, true);
JSDocInfo jsDocInfo = handleJsDoc(node, irNode);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, node);
return irNode;
}
private Node transformNumberAsString(NumberLiteral literalNode) {
Node irNode = newStringNode(getStringValue(literalNode.getNumber()));
JSDocInfo jsDocInfo = handleJsDoc(literalNode, irNode);
if (jsDocInfo
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, literalNode);
return irNode;
}
private static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
private void setSourceInfo(Node irNode, AstNode node) {
if (irNode.getLineno() == -1) {
// If we didn't already set the line, then set it now. This avoids
// cases like ParenthesizedExpression where we just return a previous
// node, but don't want the new node to get its parent's line number.
int lineno = node.getLineno();
irNode.setLineno(lineno);
int charno = position2charno(node.getAbsolutePosition());
irNode.setCharno(charno);
maybeSetLengthFrom(irNode, node);
}
}
/**
* Creates a JsDocInfoParser and parses the JsDoc string.
*
* Used both for handling individual JSDoc comments and for handling
* file-level JSDoc comments (@fileoverview and @license).
*
* @param node The JsDoc Comment node to parse.
* @param irNode
* @return A JsDocInfoParser. Will contain either fileoverview JsDoc, or
* normal JsDoc, or no JsDoc (if the method parses to the wrong level).
*/
private JsDocInfoParser createJsDocInfoParser(Comment node, Node irNode) {
String comment = node.getValue();
int lineno = node.getLineno();
int position = node.getAbsolutePosition();
// The JsDocInfoParser expects the comment without the initial '/**'.
int numOpeningChars = 3;
JsDocInfoParser jsdocParser =
new JsDocInfoParser(
new JsDocTokenStream(comment.substring(numOpeningChars),
lineno,
position2charno(position) + numOpeningChars),
node,
irNode,
config,
errorReporter);
jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder);
jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo);
jsdocParser.parse();
return jsdocParser;
}
// Set the length on the node if we're in IDE mode.
private void maybeSetLengthFrom(Node node, AstNode source) {
if (config.isIdeMode) {
node.setLength(source.getLength());
}
}
private int position2charno(int position) {
int lineIndex = sourceString.lastIndexOf('\n', position);
if (lineIndex == -1) {
return position;
} else {
// Subtract one for initial position being 0.
return position - lineIndex - 1;
}
}
private Node justTransform(AstNode node) {
return transformDispatcher.process(node);
}
private class TransformDispatcher extends TypeSafeDispatcher<Node> {
private Node processGeneric(
com.google.javascript.rhino.head.Node n) {
Node node = newNode(transformTokenType(n.getType()));
for (com.google.javascript.rhino.head.Node child : n) {
node.addChildToBack(transform((AstNode)child));
}
return
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>Lineno());
node.setCharno(position2charno(exprNode.getAbsolutePosition()));
maybeSetLengthFrom(node, exprNode);
return node;
}
@Override
Node processNumberLiteral(NumberLiteral literalNode) {
return newNumberNode(literalNode.getNumber());
}
@Override
Node processObjectLiteral(ObjectLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.OBJECTLIT);
for (ObjectProperty el : literalNode.getElements()) {
if (config.languageMode == LanguageMode.ECMASCRIPT3) {
if (el.isGetter()) {
reportGetter(el);
continue;
} else if (el.isSetter()) {
reportSetter(el);
continue;
}
}
Node key = transformAsString(el.getLeft());
key.setType(Token.STRING_KEY);
Node value = transform(el.getRight());
if (el.isGetter()) {
key.setType(Token.GETTER_DEF);
Preconditions.checkState(value.isFunction());
if (getFnParamNode(value).hasChildren()) {
reportGetterParam(el.getLeft());
}
} else if (el.isSetter()) {
key.setType(Token.SETTER_DEF);
Preconditions.checkState(value.isFunction());
if (!getFnParamNode(value).hasOneChild()) {
reportSetterParam(el.getLeft());
}
}
key.addChildToFront(value);
node.addChildToBack(key);
}
return node;
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
Node getFnParamNode(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.isFunction());
return fnNode.getFirstChild().getNext();
}
@Override
Node processObjectProperty(ObjectProperty propertyNode) {
return processInfixExpression(propertyNode);
}
@Override
Node processParenthesizedExpression(ParenthesizedExpression exprNode) {
Node node = transform(exprNode.getExpression());
node.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE);
return node;
}
@Override
Node processPropertyGet(PropertyGet getNode) {
Node leftChild = transform(getNode.getTarget());
Node newNode = newNode(
Token.GETPROP, leftChild, transformAsString(getNode.getProperty()));
newNode.setLineno(leftChild.getLineno());
newNode.setCharno(leftChild.getCharno());
maybeSetLengthFrom(newNode, getNode);
return newNode;
}
@Override
Node processRegExpLiteral(RegExpLiteral literalNode) {
Node literalStringNode = newStringNode(literalNode.getValue());
// assume it's on the same line.
literalStringNode.setLineno(literalNode.getLineno());
maybeSetLengthFrom(literalStringNode, literalNode);
Node node = newNode(Token.REGEXP, literalStringNode);
String flags = literalNode.getFlags();
if (flags != null && !flags.isEmpty()) {
Node flagsNode = newStringNode(flags);
// Assume the flags are on the same line as the literal node.
flagsNode.setLineno(literalNode.getLineno());
maybeSetLengthFrom(flagsNode,
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> literalNode);
node.addChildToBack(flagsNode);
}
return node;
}
@Override
Node processReturnStatement(ReturnStatement statementNode) {
Node node = newNode(Token.RETURN);
if (statementNode.getReturnValue() != null) {
node.addChildToBack(transform(statementNode.getReturnValue()));
}
return node;
}
@Override
Node processScope(Scope scopeNode) {
return processGeneric(scopeNode);
}
@Override
Node processStringLiteral(StringLiteral literalNode) {
String value = literalNode.getValue();
Node n = newStringNode(value);
if (value.indexOf('\u000B') != -1) {
// NOTE(nicksantos): In JavaScript, there are 3 ways to
// represent a vertical tab: \v, \x0B, \u000B.
// The \v notation was added later, and is not understood
// on IE. So we need to preserve it as-is. This is really
// obnoxious, because we do not have a good way to represent
// how the original string was encoded without making the
// representation of strings much more complicated.
//
// To handle this, we look at the original source test, and
// mark the string as \v-encoded or not. If a string is
// \v encoded, then all the vertical tabs in that string
// will be encoded with a \v.
int start = literalNode.getAbsolutePosition();
int end = start + literalNode.getLength();
if (start < sourceString.length() &&
(sourceString.substring(
start, Math.min(sourceString.length(), end))
.indexOf("\\v") != -1)) {
n.putBooleanProp(Node.SLASH_V, true);
}
}
return n;
}
@Override
Node processSwitchCase(SwitchCase caseNode) {
Node node;
if (caseNode.isDefault()) {
node = newNode(Token.DEFAULT_CASE);
} else {
AstNode expr = caseNode.getExpression();
node = newNode(Token.CASE, transform(expr));
}
Node block = newNode(Token.BLOCK);
block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
block.setLineno(caseNode.getLineno());
block.setCharno(position2charno(caseNode.getAbsolutePosition()));
maybeSetLengthFrom(block, caseNode);
if (caseNode.getStatements() != null) {
for (AstNode child : caseNode.getStatements()) {
block.addChildToBack(transform(child));
}
}
node.addChildToBack(block);
return node;
}
@Override
Node processSwitchStatement(SwitchStatement statementNode) {
Node node = newNode(Token.SWITCH,
transform(statementNode.getExpression()));
for (AstNode child : statementNode.getCases()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processThrowStatement(ThrowStatement statementNode) {
return newNode(Token.THROW,
transform(statementNode.getExpression()));
}
@Override
Node processTryStatement(TryStatement statementNode) {
Node node = newNode(Token.TRY,
transformBlock(statementNode.getTryBlock()));
Node block = newNode(Token.BLOCK);
boolean lineSet = false;
for (CatchClause cc :
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.deps;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
/**
* A sorted list of inputs with dependency information. Uses a stable
* topological sort to make sure that an input always comes after its
* dependencies.
*
* Also exposes other information about the inputs, like which inputs
* do not provide symbols.
*
* @author nicksantos@google.com (Nick Santos)
*/
public class SortedDependencies<INPUT extends DependencyInfo> {
private final List<INPUT> inputs;
// A topologically sorted list of the inputs.
private final List<INPUT> sortedList;
// A list of all the inputs that do not have provides.
private final List<INPUT> noProvides;
private final Map<String, INPUT> provideMap = Maps.newHashMap();
public SortedDependencies(List<INPUT> inputs)
throws CircularDependencyException {
this.inputs = Lists.newArrayList(inputs);
noProvides = Lists.newArrayList();
// Collect all symbols provided in these files.
for (INPUT input : inputs) {
Collection<String> currentProvides = input.getProvides();
if (currentProvides.isEmpty()) {
noProvides.add(input);
}
for (String provide : currentProvides) {
provideMap.put(provide, input);
}
}
// Get the direct dependencies.
final Multimap<INPUT, INPUT> deps = HashMultimap.create();
for (INPUT input : inputs) {
for (String req : input.getRequires()) {
INPUT dep = provideMap.get(req);
if (dep != null && dep != input) {
deps.put(input, dep);
}
}
}
// Sort the inputs by sucking in 0-in-degree nodes until we're done.
sortedList = topologicalStableSort(inputs,
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> deps);
// The dependency graph of inputs has a cycle iff sortedList is a proper
// subset of inputs. Also, it has a cycle iff the subgraph
// (inputs - sortedList) has a cycle. It's fairly easy to prove this
// by the lemma that a graph has a cycle iff it has a subgraph where
// no nodes have out-degree 0. I'll leave the proof of this as an exercise
// to the reader.
if (sortedList.size() < inputs.size()) {
List<INPUT> subGraph = Lists.newArrayList(inputs);
subGraph.removeAll(sortedList);
throw new CircularDependencyException(
cycleToString(findCycle(subGraph, deps)));
}
}
/**
* Return the input that gives us the given symbol.
* @throws MissingProvideException An exception if there is no
* input for this symbol.
*/
public INPUT getInputProviding(String symbol)
throws MissingProvideException {
if (provideMap.containsKey(symbol)) {
return provideMap.get(symbol);
}
throw new MissingProvideException(symbol);
}
/**
* Return the input that gives us the given symbol, or null.
*/
public INPUT maybeGetInputProviding(String symbol) {
return provideMap.get(symbol);
}
/**
* Returns the first circular dependency found. Expressed as a list of
* items in reverse dependency order (the second element depends on the
* first, etc.).
*/
private List<INPUT> findCycle(
List<INPUT> subGraph, Multimap<INPUT, INPUT> deps) {
return findCycle(subGraph.get(0), Sets.<INPUT>newHashSet(subGraph),
deps, Sets.<INPUT>newHashSet());
}
private List<INPUT> findCycle(
INPUT current, Set<INPUT> subGraph, Multimap<INPUT, INPUT> deps,
Set<INPUT> covered) {
if (covered.add(current)) {
List<INPUT> cycle = findCycle(
findRequireInSubGraphOrFail(current, subGraph),
subGraph, deps, covered);
// Don't add the input to the list if the cycle has closed already.
if (cycle.get(0) != cycle.get(cycle.size() - 1)) {
cycle.add(current);
}
return cycle;
} else {
// Explicitly use the add() method, to prevent a generics constructor
// warning that is dumb. The condition it's protecting is
// obscure, and I think people have proposed that it be removed.
List<INPUT> cycle = Lists.<INPUT>newArrayList();
cycle.add(current);
return cycle;
}
}
private INPUT findRequireInSubGraphOrFail(INPUT input, Set<INPUT> subGraph) {
for (String symbol : input.getRequires()) {
INPUT candidate = provideMap.get(symbol);
if (subGraph.contains(candidate)) {
return candidate;
}
}
throw new IllegalStateException("no require found in subgraph");
}
/**
* @param cycle A cycle in reverse-dependency order.
*/
private String cycleToString(List<INPUT> cycle) {
List<String> symbols = Lists.newArrayList();
for (int i = cycle.size() - 1; i >= 0; i--) {
symbols.add(cycle.get(i).getProvides().iterator().next());
}
symbols.add(symbols.get(0));
return Joiner.
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>on(" -> ").join(symbols);
}
public List<INPUT> getSortedList() {
return Collections.<INPUT>unmodifiableList(sortedList);
}
/**
* Gets all the dependencies of the given roots. The inputs must be returned
* in a stable order. In other words, if A comes before B, and A does not
* transitively depend on B, then A must also come before B in the returned
* list.
*/
public List<INPUT> getSortedDependenciesOf(List<INPUT> roots) {
return getDependenciesOf(roots, true);
}
/**
* Gets all the dependencies of the given roots. The inputs must be returned
* in a stable order. In other words, if A comes before B, and A does not
* transitively depend on B, then A must also come before B in the returned
* list.
*
* @param sorted If true, get them in topologically sorted order. If false,
* get them in the original order they were passed to the compiler.
*/
public List<INPUT> getDependenciesOf(List<INPUT> roots, boolean sorted) {
Preconditions.checkArgument(inputs.containsAll(roots));
Set<INPUT> included = Sets.newHashSet();
Deque<INPUT> worklist = new ArrayDeque<INPUT>(roots);
while (!worklist.isEmpty()) {
INPUT current = worklist.pop();
if (included.add(current)) {
for (String req : current.getRequires()) {
INPUT dep = provideMap.get(req);
if (dep != null) {
worklist.add(dep);
}
}
}
}
ImmutableList.Builder<INPUT> builder = ImmutableList.builder();
for (INPUT current : (sorted ? sortedList : inputs)) {
if (included.contains(current)) {
builder.add(current);
}
}
return builder.build();
}
public List<INPUT> getInputsWithoutProvides() {
return Collections.<INPUT>unmodifiableList(noProvides);
}
private static <T> List<T> topologicalStableSort(
List<T> items, Multimap<T, T> deps) {
if (items.size() == 0) {
// Priority queue blows up if we give it a size of 0. Since we need
// to special case this either way, just bail out.
return Lists.newArrayList();
}
final Map<T, Integer> originalIndex = Maps.newHashMap();
for (int i = 0; i < items.size(); i++) {
originalIndex.put(items.get(i), i);
}
PriorityQueue<T> inDegreeZero = new PriorityQueue<T>(items.size(),
new Comparator<T>() {
@Override
public int compare(T a, T b) {
return originalIndex.get(a).intValue() -
originalIndex.get(b).intValue();
}
});
List<T> result = Lists.newArrayList();
Multiset<T> inDegree = HashMultiset.create();
Multimap<T, T> reverseDeps = ArrayListMultimap.create();
Multimaps.invertFrom(deps, reverseDeps);
// First, add all the inputs with in-degree 0.
for (T item : items) {
Collection<T> itemDeps = deps.get(item);
inDegree.add(item, itemDeps.size());
if (itemDeps.isEmpty()) {
inDegreeZero.add(
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> referenceMap) {
// TODO(bashir) In hot-swap version this means that for global scope we
// only go through all global variables accessed in the modified file not
// all global variables. This should be fixed.
// Check all vars after finishing a scope
for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) {
Var v = it.next();
checkVar(t, v, referenceMap.getReferences(v).references);
}
}
/**
* If the variable is declared more than once in a basic block, generate a
* warning. Also check if a variable is used in a given scope before it is
* declared, which suggest a likely error. Relies on the fact that
* references is in parse-tree order.
*/
private void checkVar(NodeTraversal t, Var v, List<Reference> references) {
blocksWithDeclarations.clear();
boolean isDeclaredInScope = false;
boolean isUnhoistedNamedFunction = false;
Reference hoistedFn = null;
// Look for hoisted functions.
for (Reference reference : references) {
if (reference.isHoistedFunction()) {
blocksWithDeclarations.add(reference.getBasicBlock());
isDeclaredInScope = true;
hoistedFn = reference;
break;
} else if (NodeUtil.isFunctionDeclaration(
reference.getNode().getParent())) {
isUnhoistedNamedFunction = true;
}
}
for (Reference reference : references) {
if (reference == hoistedFn) {
continue;
}
BasicBlock basicBlock = reference.getBasicBlock();
boolean isDeclaration = reference.isDeclaration();
boolean allowDupe =
SyntacticScopeCreator.hasDuplicateDeclarationSuppression(
reference.getNode(), v);
if (isDeclaration && !allowDupe) {
// Look through all the declarations we've found so far, and
// check if any of them are before this block.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (declaredBlock.provablyExecutesBefore(basicBlock)) {
// TODO(johnlenz): Fix AST generating clients that so they would
// have property StaticSourceFile attached at each node. Or
// better yet, make sure the generated code never violates
// the requirement to pass aggressive var check!
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
checkLevel,
REDECLARED_VARIABLE, v.name));
break;
}
}
}
if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) {
// Only allow an unhoisted named function to be used within the
// block it is declared.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (!declaredBlock.provablyExecutesBefore(basicBlock)) {
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
AMBIGUOUS_FUNCTION_DECL, v.name));
break;
}
}
}
if (!isDeclaration && !isDeclaredInScope) {
// Don't check the order of refer in externs files.
if (!reference.getNode().isFromExterns()) {
// Special case to deal with var goog = goog || {}
Node grandparent = reference.getGrandparent();
if (grandparent.isName()
&& grandparent.getString() == v
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> JS code
* <li>checks for undefined variables
* <li>performs optimizations such as constant folding and constants inlining
* <li>renames variables (to short names)
* <li>outputs compact JavaScript code
* </ul>
*
* External variables are declared in 'externs' files. For instance, the file
* may include definitions for global javascript/browser objects such as
* window, document.
*
*/
public class Compiler extends AbstractCompiler {
static final String SINGLETON_MODULE_NAME = "[singleton]";
static final DiagnosticType MODULE_DEPENDENCY_ERROR =
DiagnosticType.error("JSC_MODULE_DEPENDENCY_ERROR",
"Bad dependency: {0} -> {1}. "
+ "Modules must be listed in dependency order.");
static final DiagnosticType MISSING_ENTRY_ERROR = DiagnosticType.error(
"JSC_MISSING_ENTRY_ERROR",
"required entry point \"{0}\" never provided");
private static final String CONFIG_RESOURCE =
"com.google.javascript.jscomp.parsing.ParserConfig";
CompilerOptions options = null;
private PassConfig passes = null;
// The externs inputs
private List<CompilerInput> externs;
// The JS source modules
private List<JSModule> modules;
// The graph of the JS source modules. Must be null if there are less than
// 2 modules, because we use this as a signal for which passes to run.
private JSModuleGraph moduleGraph;
// The JS source inputs
private List<CompilerInput> inputs;
// error manager to which error management is delegated
private ErrorManager errorManager;
// Warnings guard for filtering warnings.
private WarningsGuard warningsGuard;
// Compile-time injected libraries. The node points to the last node of
// the library, so code can be inserted after.
private final Map<String, Node> injectedLibraries = Maps.newLinkedHashMap();
// Parse tree root nodes
Node externsRoot;
Node jsRoot;
Node externAndJsRoot;
private Map<InputId, CompilerInput> inputsById;
/** The source code map */
private SourceMap sourceMap;
/** The externs created from the exports. */
private String externExports = null;
/**
* Ids for function inlining so that each declared name remains
* unique.
*/
private int uniqueNameId = 0;
/**
* Whether to assume there are references to the RegExp Global object
* properties.
*/
private boolean hasRegExpGlobalReferences = true;
/** The function information map */
private FunctionInformationMap functionInformationMap;
/** Debugging information */
private final StringBuilder debugLog = new StringBuilder();
/** Detects Google-specific coding conventions. */
CodingConvention defaultCodingConvention = new ClosureCodingConvention();
private JSTypeRegistry typeRegistry;
private Config parserConfig = null;
private ReverseAbstractInterpreter abstractInterpreter;
private TypeValidator typeValidator;
public PerformanceTracker tracker;
// The oldErrorReporter exists so we can get errors from the JSTypeRegistry.
private final com.google.javascript.rhino.ErrorReporter oldErrorReporter =
RhinoErrorReporter.forOldRhino(this);
// This error reporter gets the messages from the current Rhino parser.
private final ErrorReporter defaultErrorReporter =
RhinoErrorReporter.forNewRhino(this);
/** Error strings used for reporting JSErrors */
public static final DiagnosticType OPTIMIZE_LOOP_ERROR = DiagnosticType.error(
"JSC_
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>(printer);
}
}
// DiagnosticGroups override the plain checkTypes option.
if (options.enables(DiagnosticGroups.CHECK_TYPES)) {
options.checkTypes = true;
} else if (options.disables(DiagnosticGroups.CHECK_TYPES)) {
options.checkTypes = false;
} else if (!options.checkTypes) {
// If DiagnosticGroups did not override the plain checkTypes
// option, and checkTypes is enabled, then turn off the
// parser type warnings.
options.setWarningLevel(
DiagnosticGroup.forType(
RhinoErrorReporter.TYPE_PARSE_ERROR),
CheckLevel.OFF);
}
if (options.checkGlobalThisLevel.isOn() &&
!options.disables(DiagnosticGroups.GLOBAL_THIS)) {
options.setWarningLevel(
DiagnosticGroups.GLOBAL_THIS,
options.checkGlobalThisLevel);
}
if (options.getLanguageIn() == LanguageMode.ECMASCRIPT5_STRICT) {
options.setWarningLevel(
DiagnosticGroups.ES5_STRICT,
CheckLevel.ERROR);
}
// Initialize the warnings guard.
List<WarningsGuard> guards = Lists.newArrayList();
guards.add(
new SuppressDocWarningsGuard(
getDiagnosticGroups().getRegisteredGroups()));
guards.add(options.getWarningsGuard());
ComposeWarningsGuard composedGuards = new ComposeWarningsGuard(guards);
// All passes must run the variable check. This synthesizes
// variables later so that the compiler doesn't crash. It also
// checks the externs file for validity. If you don't want to warn
// about missing variable declarations, we shut that specific
// error off.
if (!options.checkSymbols &&
!composedGuards.enables(DiagnosticGroups.CHECK_VARIABLES)) {
composedGuards.addGuard(new DiagnosticGroupWarningsGuard(
DiagnosticGroups.CHECK_VARIABLES, CheckLevel.OFF));
}
this.warningsGuard = composedGuards;
}
/**
* Initializes the instance state needed for a compile job.
* @deprecated Convert your arrays to lists and use the list-based API.
*/
@Deprecated
public void init(JSSourceFile[] externs, JSSourceFile[] inputs,
CompilerOptions options) {
init(Lists.<JSSourceFile>newArrayList(externs),
Lists.<JSSourceFile>newArrayList(inputs), options);
}
/**
* Initializes the instance state needed for a compile job.
*/
public <T1 extends SourceFile, T2 extends SourceFile> void init(
List<T1> externs,
List<T2> inputs,
CompilerOptions options) {
JSModule module = new JSModule(SINGLETON_MODULE_NAME);
for (SourceFile input : inputs) {
module.add(input);
}
initModules(externs, Lists.newArrayList(module), options);
}
/**
* Initializes the instance state needed for a compile job if the sources
* are in modules.
* @deprecated Convert your arrays to lists and use the list-based API.
*/
@Deprecated
public void init(JSSourceFile[] externs, JSModule[] modules,
CompilerOptions options) {
initModules(Lists.<SourceFile>newArrayList(externs),
Lists.<JSModule>newArrayList(modules), options);
}
/**
* Initializes the instance state needed for a compile job if the sources
* are in modules.
*/
public <T extends SourceFile> void
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> initModules(
List<T> externs, List<JSModule> modules, CompilerOptions options) {
initOptions(options);
checkFirstModule(modules);
fillEmptyModules(modules);
this.externs = makeCompilerInput(externs, true);
// Generate the module graph, and report any errors in the module
// specification as errors.
this.modules = modules;
if (modules.size() > 1) {
try {
this.moduleGraph = new JSModuleGraph(modules);
} catch (JSModuleGraph.ModuleDependenceException e) {
// problems with the module format. Report as an error. The
// message gives all details.
report(JSError.make(MODULE_DEPENDENCY_ERROR,
e.getModule().getName(), e.getDependentModule().getName()));
return;
}
} else {
this.moduleGraph = null;
}
this.inputs = getAllInputsFromModules(modules);
initBasedOnOptions();
initInputsByIdMap();
}
/**
* Do any initialization that is dependent on the compiler options.
*/
private void initBasedOnOptions() {
// Create the source map if necessary.
if (options.sourceMapOutputPath != null) {
sourceMap = options.sourceMapFormat.getInstance();
sourceMap.setPrefixMappings(options.sourceMapLocationMappings);
}
}
private <T extends SourceFile> List<CompilerInput> makeCompilerInput(
List<T> files, boolean isExtern) {
List<CompilerInput> inputs = Lists.newArrayList();
for (T file : files) {
inputs.add(new CompilerInput(file, isExtern));
}
return inputs;
}
private static final DiagnosticType EMPTY_MODULE_LIST_ERROR =
DiagnosticType.error("JSC_EMPTY_MODULE_LIST_ERROR",
"At least one module must be provided");
private static final DiagnosticType EMPTY_ROOT_MODULE_ERROR =
DiagnosticType.error("JSC_EMPTY_ROOT_MODULE_ERROR",
"Root module '{0}' must contain at least one source code input");
/**
* Verifies that at least one module has been provided and that the first one
* has at least one source code input.
*/
private void checkFirstModule(List<JSModule> modules) {
if (modules.isEmpty()) {
report(JSError.make(EMPTY_MODULE_LIST_ERROR));
} else if (modules.get(0).getInputs().isEmpty() && modules.size() > 1) {
// The root module may only be empty if there is exactly 1 module.
report(JSError.make(EMPTY_ROOT_MODULE_ERROR,
modules.get(0).getName()));
}
}
/**
* Empty modules get an empty "fill" file, so that we can move code into
* an empty module.
*/
static String createFillFileName(String moduleName) {
return "[" + moduleName + "]";
}
/**
* Fill any empty modules with a place holder file. It makes any cross module
* motion easier.
*/
private static void fillEmptyModules(List<JSModule> modules) {
for (JSModule module : modules) {
if (module.getInputs().isEmpty()) {
module.add(SourceFile.fromCode(
createFillFileName(module.getName()), ""));
}
}
}
/**
* Rebuilds the internal list of inputs by iterating over all modules.
* This is necessary if inputs have been added to
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> or removed from a module
* after the {@link #init(JSSourceFile[], JSModule[], CompilerOptions)} call.
*/
public void rebuildInputsFromModules() {
inputs = getAllInputsFromModules(modules);
initInputsByIdMap();
}
/**
* Builds a single list of all module inputs. Verifies that it contains no
* duplicates.
*/
private static List<CompilerInput> getAllInputsFromModules(
List<JSModule> modules) {
List<CompilerInput> inputs = Lists.newArrayList();
Map<String, JSModule> inputMap = Maps.newHashMap();
for (JSModule module : modules) {
for (CompilerInput input : module.getInputs()) {
String inputName = input.getName();
// NOTE(nicksantos): If an input is in more than one module,
// it will show up twice in the inputs list, and then we
// will get an error down the line.
inputs.add(input);
inputMap.put(inputName, module);
}
}
return inputs;
}
static final DiagnosticType DUPLICATE_INPUT =
DiagnosticType.error("JSC_DUPLICATE_INPUT", "Duplicate input: {0}");
static final DiagnosticType DUPLICATE_EXTERN_INPUT =
DiagnosticType.error("JSC_DUPLICATE_EXTERN_INPUT",
"Duplicate extern input: {0}");
/**
* Creates a map to make looking up an input by name fast. Also checks for
* duplicate inputs.
*/
void initInputsByIdMap() {
inputsById = new HashMap<InputId, CompilerInput>();
for (CompilerInput input : externs) {
InputId id = input.getInputId();
CompilerInput previous = putCompilerInput(id, input);
if (previous != null) {
report(JSError.make(DUPLICATE_EXTERN_INPUT, input.getName()));
}
}
for (CompilerInput input : inputs) {
InputId id = input.getInputId();
CompilerInput previous = putCompilerInput(id, input);
if (previous != null) {
report(JSError.make(DUPLICATE_INPUT, input.getName()));
}
}
}
public Result compile(
SourceFile extern, SourceFile input, CompilerOptions options) {
return compile(Lists.newArrayList(extern), Lists.newArrayList(input), options);
}
/**
* @deprecated Convert your arrays to lists and use the list-based API.
*/
@Deprecated
public Result compile(
SourceFile extern, JSSourceFile[] input, CompilerOptions options) {
return compile(Lists.newArrayList(extern), Lists.newArrayList(input), options);
}
/**
* @deprecated Convert your arrays to lists and use the list-based
* compileModules method.
*/
@Deprecated
public Result compile(
JSSourceFile extern, JSModule[] modules, CompilerOptions options) {
return compileModules(
Lists.newArrayList(extern), Lists.newArrayList(modules), options);
}
/**
* Compiles a list of inputs.
* @deprecated Convert your arrays to lists and use the list-based compile
* method.
*/
@Deprecated
public Result compile(JSSourceFile[] externs,
JSSourceFile[] inputs,
CompilerOptions options) {
return compile(Lists.<SourceFile>newArrayList(externs),
Lists.<SourceFile>newArrayList(inputs),
options);
}
/**
* Compiles a list of inputs.
*/
public <T1 extends SourceFile, T
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>2 extends SourceFile> Result compile(
List<T1> externs, List<T2> inputs, CompilerOptions options) {
// The compile method should only be called once.
Preconditions.checkState(jsRoot == null);
try {
init(externs, inputs, options);
if (hasErrors()) {
return getResult();
}
return compile();
} finally {
Tracer t = newTracer("generateReport");
errorManager.generateReport();
stopTracer(t, "generateReport");
}
}
/**
* Compiles a list of modules.
* @deprecated Convert your arrays to lists and use the list-based
* compileModules method.
*/
@Deprecated
public Result compile(JSSourceFile[] externs,
JSModule[] modules,
CompilerOptions options) {
return compileModules(Lists.<SourceFile>newArrayList(externs),
Lists.<JSModule>newArrayList(modules),
options);
}
/**
* Compiles a list of modules.
*/
public <T extends SourceFile> Result compileModules(List<T> externs,
List<JSModule> modules, CompilerOptions options) {
// The compile method should only be called once.
Preconditions.checkState(jsRoot == null);
try {
initModules(externs, modules, options);
if (hasErrors()) {
return getResult();
}
return compile();
} finally {
Tracer t = newTracer("generateReport");
errorManager.generateReport();
stopTracer(t, "generateReport");
}
}
private Result compile() {
return runInCompilerThread(new Callable<Result>() {
@Override
public Result call() throws Exception {
compileInternal();
return getResult();
}
});
}
/**
* Disable threads. This is for clients that run on AppEngine and
* don't have threads.
*/
public void disableThreads() {
useThreads = false;
}
@SuppressWarnings("unchecked")
<T> T runInCompilerThread(final Callable<T> callable) {
final boolean dumpTraceReport = options != null && options.tracer.isOn();
T result = null;
final Throwable[] exception = new Throwable[1];
Callable<T> bootCompilerThread = new Callable<T>() {
@Override
public T call() {
try {
compilerThread = Thread.currentThread();
if (dumpTraceReport) {
Tracer.initCurrentThreadTrace();
}
return callable.call();
} catch (Throwable e) {
exception[0] = e;
} finally {
compilerThread = null;
if (dumpTraceReport) {
Tracer.logAndClearCurrentThreadTrace();
}
}
return null;
}
};
Preconditions.checkState(
compilerThread == null || compilerThread == Thread.currentThread(),
"Please do not share the Compiler across threads");
// If the compiler thread is available, use it.
if (useThreads && compilerThread == null) {
try {
result = compilerExecutor.submit(bootCompilerThread).get();
} catch (InterruptedException e) {
throw Throwables.propagate(e);
} catch (ExecutionException e) {
throw Throwables.propagate(e);
}
} else {
try {
result = callable.call();
} catch (Exception e) {
exception[0] = e;
}
}
// Pass on any exception caught by the runnable object.
if (exception[0] != null) {
throw new RuntimeException
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> continue;
}
if (options.transformAMDToCJSModules) {
new TransformAMDToCJSModule(this).process(null, root);
}
if (options.processCommonJSModules) {
ProcessCommonJSModules cjs = new ProcessCommonJSModules(this,
options.commonJSModulePathPrefix);
cjs.process(null, root);
JSModule m = cjs.getModule();
if (m != null) {
modulesByName.put(m.getName(), m);
modulesByInput.put(input, m);
}
}
}
if (options.processCommonJSModules) {
List<JSModule> modules = Lists.newArrayList(modulesByName.values());
if (!modules.isEmpty()) {
this.modules = modules;
this.moduleGraph = new JSModuleGraph(this.modules);
}
for (JSModule module : modules) {
for (CompilerInput input : module.getInputs()) {
for (String require : input.getRequires()) {
JSModule dependency = modulesByName.get(require);
if (dependency == null) {
report(JSError.make(MISSING_ENTRY_ERROR, require));
} else {
module.addDependency(dependency);
}
}
}
}
try {
modules = Lists.newArrayList();
for (CompilerInput input : this.moduleGraph.manageDependencies(
options.dependencyOptions, inputs)) {
modules.add(modulesByInput.get(input));
}
this.modules = modules;
this.moduleGraph = new JSModuleGraph(modules);
} catch (Exception e) {
Throwables.propagate(e);
}
}
}
public Node parse(SourceFile file) {
initCompilerOptionsIfTesting();
addToDebugLog("Parsing: " + file.getName());
return new JsAst(file).getAstRoot(this);
}
private int syntheticCodeId = 0;
@Override
Node parseSyntheticCode(String js) {
CompilerInput input = new CompilerInput(
SourceFile.fromCode(" [synthetic:" + (++syntheticCodeId) + "] ", js));
putCompilerInput(input.getInputId(), input);
return input.getAstRoot(this);
}
/**
* Allow subclasses to override the default CompileOptions object.
*/
protected CompilerOptions newCompilerOptions() {
return new CompilerOptions();
}
void initCompilerOptionsIfTesting() {
if (options == null) {
// initialization for tests that don't initialize the compiler
// by the normal mechanisms.
initOptions(newCompilerOptions());
}
}
@Override
Node parseSyntheticCode(String fileName, String js) {
initCompilerOptionsIfTesting();
return parse(SourceFile.fromCode(fileName, js));
}
@Override
Node parseTestCode(String js) {
initCompilerOptionsIfTesting();
CompilerInput input = new CompilerInput(
SourceFile.fromCode("[testcode]", js));
if (inputsById == null) {
inputsById = Maps.newHashMap();
}
putCompilerInput(input.getInputId(), input);
return input.getAstRoot(this);
}
@Override
ErrorReporter getDefaultErrorReporter() {
return defaultErrorReporter;
}
//------------------------------------------------------------------------
// Convert back to source code
//------------------------------------------------------------------------
/**
* Converts the main parse tree back to JS code.
*/
public String toSource() {
return runInCompilerThread(new Callable<String>() {
@Override
public
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> String call() throws Exception {
Tracer tracer = newTracer("toSource");
try {
CodeBuilder cb = new CodeBuilder();
if (jsRoot != null) {
int i = 0;
for (Node scriptNode = jsRoot.getFirstChild();
scriptNode != null;
scriptNode = scriptNode.getNext()) {
toSource(cb, i++, scriptNode);
}
}
return cb.toString();
} finally {
stopTracer(tracer, "toSource");
}
}
});
}
/**
* Converts the parse tree for each input back to JS code.
*/
public String[] toSourceArray() {
return runInCompilerThread(new Callable<String[]>() {
@Override
public String[] call() throws Exception {
Tracer tracer = newTracer("toSourceArray");
try {
int numInputs = inputs.size();
String[] sources = new String[numInputs];
CodeBuilder cb = new CodeBuilder();
for (int i = 0; i < numInputs; i++) {
Node scriptNode = inputs.get(i).getAstRoot(Compiler.this);
cb.reset();
toSource(cb, i, scriptNode);
sources[i] = cb.toString();
}
return sources;
} finally {
stopTracer(tracer, "toSourceArray");
}
}
});
}
/**
* Converts the parse tree for a module back to JS code.
*/
public String toSource(final JSModule module) {
return runInCompilerThread(new Callable<String>() {
@Override
public String call() throws Exception {
List<CompilerInput> inputs = module.getInputs();
int numInputs = inputs.size();
if (numInputs == 0) {
return "";
}
CodeBuilder cb = new CodeBuilder();
for (int i = 0; i < numInputs; i++) {
Node scriptNode = inputs.get(i).getAstRoot(Compiler.this);
if (scriptNode == null) {
throw new IllegalArgumentException(
"Bad module: " + module.getName());
}
toSource(cb, i, scriptNode);
}
return cb.toString();
}
});
}
/**
* Converts the parse tree for each input in a module back to JS code.
*/
public String[] toSourceArray(final JSModule module) {
return runInCompilerThread(new Callable<String[]>() {
@Override
public String[] call() throws Exception {
List<CompilerInput> inputs = module.getInputs();
int numInputs = inputs.size();
if (numInputs == 0) {
return new String[0];
}
String[] sources = new String[numInputs];
CodeBuilder cb = new CodeBuilder();
for (int i = 0; i < numInputs; i++) {
Node scriptNode = inputs.get(i).getAstRoot(Compiler.this);
if (scriptNode == null) {
throw new IllegalArgumentException(
"Bad module input: " + inputs.get(i).getName());
}
cb.reset();
toSource(cb, i, scriptNode);
sources[i] = cb.toString();
}
return sources;
}
});
}
/**
* Writes out JS code from a root node. If printing input delimiters, this
* method will attach a comment to the start of the text indicating which
* input the output derived from. If there were any preserve annotations
* within the root
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> AST
* specifically for that target.
*/
public void processDefines() {
(new DefaultPassConfig(options)).processDefines.create(this)
.process(externsRoot, jsRoot);
}
boolean isInliningForbidden() {
return options.propertyRenaming == PropertyRenamingPolicy.HEURISTIC ||
options.propertyRenaming ==
PropertyRenamingPolicy.AGGRESSIVE_HEURISTIC;
}
/** Control Flow Analysis. */
ControlFlowGraph<Node> computeCFG() {
logger.fine("Computing Control Flow Graph");
Tracer tracer = newTracer("computeCFG");
ControlFlowAnalysis cfa = new ControlFlowAnalysis(this, true, false);
process(cfa);
stopTracer(tracer, "computeCFG");
return cfa.getCfg();
}
public void normalize() {
logger.fine("Normalizing");
startPass("normalize");
process(new Normalize(this, false));
endPass();
}
@Override
void prepareAst(Node root) {
CompilerPass pass = new PrepareAst(this);
pass.process(null, root);
}
void recordFunctionInformation() {
logger.fine("Recording function information");
startPass("recordFunctionInformation");
RecordFunctionInformation recordFunctionInfoPass =
new RecordFunctionInformation(
this, getPassConfig().getIntermediateState().functionNames);
process(recordFunctionInfoPass);
functionInformationMap = recordFunctionInfoPass.getMap();
endPass();
}
protected final CodeChangeHandler.RecentChange recentChange =
new CodeChangeHandler.RecentChange();
private final List<CodeChangeHandler> codeChangeHandlers =
Lists.<CodeChangeHandler>newArrayList();
/** Name of the synthetic input that holds synthesized externs. */
static final String SYNTHETIC_EXTERNS = "{SyntheticVarsDeclar}";
private CompilerInput synthesizedExternsInput = null;
@Override
void addChangeHandler(CodeChangeHandler handler) {
codeChangeHandlers.add(handler);
}
@Override
void removeChangeHandler(CodeChangeHandler handler) {
codeChangeHandlers.remove(handler);
}
/**
* All passes should call reportCodeChange() when they alter
* the JS tree structure. This is verified by CompilerTestCase.
* This allows us to optimize to a fixed point.
*/
@Override
public void reportCodeChange() {
for (CodeChangeHandler handler : codeChangeHandlers) {
handler.reportChange();
}
}
@Override
public CodingConvention getCodingConvention() {
CodingConvention convention = options.getCodingConvention();
convention = convention != null ? convention : defaultCodingConvention;
return convention;
}
@Override
public boolean isIdeMode() {
return options.ideMode;
}
@Override
public boolean acceptEcmaScript5() {
switch (options.getLanguageIn()) {
case ECMASCRIPT5:
case ECMASCRIPT5_STRICT:
return true;
}
return false;
}
public LanguageMode languageMode() {
return options.getLanguageIn();
}
@Override
public boolean acceptConstKeyword() {
return options.acceptConstKeyword;
}
@Override
Config getParserConfig() {
if (parserConfig == null) {
Config.LanguageMode mode;
switch (options.getLanguageIn()) {
case ECMASCRIPT3:
mode = Config.LanguageMode.ECMASCRIPT3;
break;
case ECMASCRIPT5:
mode = Config.Language
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>) {
if (lineNumber < 1) {
return null;
}
SourceFile input = getSourceFileByName(sourceName);
if (input != null) {
return input.getLine(lineNumber);
}
return null;
}
@Override
public Region getSourceRegion(String sourceName, int lineNumber) {
if (lineNumber < 1) {
return null;
}
SourceFile input = getSourceFileByName(sourceName);
if (input != null) {
return input.getRegion(lineNumber);
}
return null;
}
//------------------------------------------------------------------------
// Package-private helpers
//------------------------------------------------------------------------
@Override
Node getNodeForCodeInsertion(JSModule module) {
if (module == null) {
if (inputs.isEmpty()) {
throw new IllegalStateException("No inputs");
}
return inputs.get(0).getAstRoot(this);
}
List<CompilerInput> moduleInputs = module.getInputs();
if (moduleInputs.size() > 0) {
return moduleInputs.get(0).getAstRoot(this);
}
throw new IllegalStateException("Root module has no inputs");
}
public SourceMap getSourceMap() {
return sourceMap;
}
VariableMap getVariableMap() {
return getPassConfig().getIntermediateState().variableMap;
}
VariableMap getPropertyMap() {
return getPassConfig().getIntermediateState().propertyMap;
}
CompilerOptions getOptions() {
return options;
}
FunctionInformationMap getFunctionalInformationMap() {
return functionInformationMap;
}
/**
* Sets the logging level for the com.google.javascript.jscomp package.
*/
public static void setLoggingLevel(Level level) {
logger.setLevel(level);
}
/** Gets the DOT graph of the AST generated at the end of compilation. */
public String getAstDotGraph() throws IOException {
if (jsRoot != null) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(this, true, false);
cfa.process(null, jsRoot);
return DotFormatter.toDot(jsRoot, cfa.getCfg());
} else {
return "";
}
}
@Override
public ErrorManager getErrorManager() {
if (options == null) {
initOptions(newCompilerOptions());
}
return errorManager;
}
@Override
List<CompilerInput> getInputsInOrder() {
return Collections.<CompilerInput>unmodifiableList(inputs);
}
/**
* Returns an unmodifiable view of the compiler inputs indexed by id.
*/
public Map<InputId, CompilerInput> getInputsById() {
return Collections.unmodifiableMap(inputsById);
}
/**
* Gets the externs in the order in which they are being processed.
*/
List<CompilerInput> getExternsInOrder() {
return Collections.<CompilerInput>unmodifiableList(externs);
}
/**
* Stores the internal compiler state just before optimization is performed.
* This can be saved and restored in order to efficiently optimize multiple
* different output targets without having to perform checking multiple times.
*
* NOTE: This does not include all parts of the compiler's internal state. In
* particular, SourceFiles and CompilerOptions are not recorded. In
* order to recreate a Compiler instance from scratch, you would need to
* call {@code init} with the same arguments as in the initial creation before
* restoring intermediate state.
*/
public static class IntermediateState implements Serializable {
private static
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> final long serialVersionUID = 1L;
Node externsRoot;
private Node jsRoot;
private List<CompilerInput> externs;
private List<CompilerInput> inputs;
private List<JSModule> modules;
private PassConfig.State passConfigState;
private JSTypeRegistry typeRegistry;
private AbstractCompiler.LifeCycleStage lifeCycleStage;
private Map<String, Node> injectedLibraries;
private IntermediateState() {}
}
/**
* Returns the current internal state, excluding the input files and modules.
*/
public IntermediateState getState() {
IntermediateState state = new IntermediateState();
state.externsRoot = externsRoot;
state.jsRoot = jsRoot;
state.externs = externs;
state.inputs = inputs;
state.modules = modules;
state.passConfigState = getPassConfig().getIntermediateState();
state.typeRegistry = typeRegistry;
state.lifeCycleStage = getLifeCycleStage();
state.injectedLibraries = Maps.newLinkedHashMap(injectedLibraries);
return state;
}
/**
* Sets the internal state to the capture given. Note that this assumes that
* the input files are already set up.
*/
public void setState(IntermediateState state) {
externsRoot = state.externsRoot;
jsRoot = state.jsRoot;
externs = state.externs;
inputs = state.inputs;
modules = state.modules;
passes = createPassConfigInternal();
getPassConfig().setIntermediateState(state.passConfigState);
typeRegistry = state.typeRegistry;
setLifeCycleStage(state.lifeCycleStage);
injectedLibraries.clear();
injectedLibraries.putAll(state.injectedLibraries);
}
@VisibleForTesting
List<CompilerInput> getInputsForTesting() {
return inputs;
}
@VisibleForTesting
List<CompilerInput> getExternsForTesting() {
return externs;
}
@Override
boolean hasRegExpGlobalReferences() {
return hasRegExpGlobalReferences;
}
@Override
void setHasRegExpGlobalReferences(boolean references) {
hasRegExpGlobalReferences = references;
}
@Override
void updateGlobalVarReferences(Map<Var, ReferenceCollection> refMapPatch,
Node collectionRoot) {
Preconditions.checkState(collectionRoot.isScript()
|| collectionRoot.isBlock());
if (globalRefMap == null) {
globalRefMap = new GlobalVarReferenceMap(getInputsInOrder(),
getExternsInOrder());
}
globalRefMap.updateGlobalVarReferences(refMapPatch, collectionRoot);
}
@Override
GlobalVarReferenceMap getGlobalVarReferences() {
return globalRefMap;
}
@Override
CompilerInput getSynthesizedExternsInput() {
if (synthesizedExternsInput == null) {
synthesizedExternsInput = newExternInput(SYNTHETIC_EXTERNS);
}
return synthesizedExternsInput;
}
@Override
public double getProgress() {
return progress;
}
@Override
void setProgress(double newProgress) {
if (newProgress > 1.0) {
progress = 1.0;
} else if (newProgress < 0.0) {
progress = 0.0;
} else {
progress = newProgress;
}
}
/**
* Replaces one file in a hot-swap mode. The given JsAst should be made
* from a
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> works correctly through
* typedefs. Alternatively, we would need to walk through the parse tree and
* unroll each reference to a {@code NamedType} to its resolved type before
* applying the rest of the analysis.<p>
*
* TODO(user): Revisit all of this logic.<p>
*
* The existing typing logic is hacky. Unresolved types should get processed
* in a more consistent way, but with the Rhino merge coming, there will be
* much that has to be changed.<p>
*
*/
class NamedType extends ProxyObjectType {
private static final long serialVersionUID = 1L;
private final String reference;
private final String sourceName;
private final int lineno;
private final int charno;
/**
* Validates the type resolution.
*/
private Predicate<JSType> validator;
/**
* Property-defining continuations.
*/
private List<PropertyContinuation> propertyContinuations = null;
/**
* Create a named type based on the reference.
*/
NamedType(JSTypeRegistry registry, String reference,
String sourceName, int lineno, int charno) {
super(registry, registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE));
Preconditions.checkNotNull(reference);
this.reference = reference;
this.sourceName = sourceName;
this.lineno = lineno;
this.charno = charno;
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, Node propertyNode) {
if (!isResolved()) {
// If this is an unresolved object type, we need to save all its
// properties and define them when it is resolved.
if (propertyContinuations == null) {
propertyContinuations = Lists.newArrayList();
}
propertyContinuations.add(
new PropertyContinuation(
propertyName, type, inferred, propertyNode));
return true;
} else {
return super.defineProperty(
propertyName, type, inferred, propertyNode);
}
}
private void finishPropertyContinuations() {
ObjectType referencedObjType = getReferencedObjTypeInternal();
if (referencedObjType != null && !referencedObjType.isUnknownType()) {
if (propertyContinuations != null) {
for (PropertyContinuation c : propertyContinuations) {
c.commit(this);
}
}
}
propertyContinuations = null;
}
/** Returns the type to which this refers (which is unknown if unresolved). */
public JSType getReferencedType() {
return getReferencedTypeInternal();
}
@Override
public String getReferenceName() {
return reference;
}
@Override
String toStringHelper(boolean forAnnotations) {
return reference;
}
@Override
public boolean hasReferenceName() {
return true;
}
@Override
boolean isNamedType() {
return true;
}
@Override
public boolean isNominalType() {
return true;
}
/**
* Two named types are equivalent if they are the same {@code
* ObjectType} object. This is complicated by the fact that isEquivalent
* is sometimes called before we have a chance to resolve the type
* names.
*
* @return {@code true} iff {@code that} == {@code this} or {@code that}
* is a {@link NamedType} whose reference is the same as ours,
* or {@code that} is the type we reference.
*/
@Override
public boolean isEquivalentTo(JSType that) {
if (this ==
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>>Flow Equations: Implement
* {@link #flowThrough(Object, LatticeElement)}.
* <li>Initial Entry Value: Implement {@link #createEntryLattice()}.
* <li>Initial Estimate: Implement {@link #createInitialEstimateLattice()}.
* </ol>
*
* <p>Upon execution of the {@link #analyze()} method, nodes of the input
* control flow graph will be annotated with a {@link FlowState} object that
* represents maximum fixed point solution. Any previous annotations at the
* nodes of the control flow graph will be lost.
*
*
* @param <N> The control flow graph's node value type.
* @param <L> Lattice element type.
*/
abstract class DataFlowAnalysis<N, L extends LatticeElement> {
private final ControlFlowGraph<N> cfg;
final JoinOp<L> joinOp;
protected final Set<DiGraphNode<N, Branch>> orderedWorkSet;
/*
* Feel free to increase this to a reasonable number if you are finding that
* more and more passes need more than 200000 steps before finding a
* fixed-point. If you just have a special case, consider calling
* {@link #analyse(int)} instead.
*/
public static final int MAX_STEPS = 200000;
/**
* Constructs a data flow analysis.
*
* <p>Typical usage
* <pre>
* DataFlowAnalysis dfa = ...
* dfa.analyze();
* </pre>
*
* {@link #analyze()} annotates the result to the control flow graph by
* means of {@link DiGraphNode#setAnnotation} without any
* modification of the graph itself. Additional calls to {@link #analyze()}
* recomputes the analysis which can be useful if the control flow graph
* has been modified.
*
* @param targetCfg The control flow graph object that this object performs
* on. Modification of the graph requires a separate call to
* {@link #analyze()}.
*
* @see #analyze()
*/
DataFlowAnalysis(ControlFlowGraph<N> targetCfg, JoinOp<L> joinOp) {
this.cfg = targetCfg;
this.joinOp = joinOp;
Comparator<DiGraphNode<N, Branch>> nodeComparator =
cfg.getOptionalNodeComparator(isForward());
if (nodeComparator != null) {
this.orderedWorkSet = Sets.newTreeSet(nodeComparator);
} else {
this.orderedWorkSet = Sets.newLinkedHashSet();
}
}
/**
* Returns the control flow graph that this analysis was performed on.
* Modifications can be done on this graph, however, the only time that the
* annotations are correct is after {@link #analyze()} is called and before
* the graph has been modified.
*/
final ControlFlowGraph<N> getCfg() {
return cfg;
}
/**
* Returns the lattice element at the exit point.
*/
L getExitLatticeElement() {
DiGraphNode<N, Branch> node = getCfg().getImplicitReturn();
FlowState<L> state = node.getAnnotation();
return state.getIn();
}
@SuppressWarnings("unchecked")
protected L join(L latticeA, L latticeB) {
return joinOp.apply(Lists.<L>newArrayList(latticeA, latticeB));
}
/**
* Checks whether the analysis is a forward flow analysis
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> or backward flow
* analysis.
*
* @return {@code true} if it is a forward analysis.
*/
abstract boolean isForward();
/**
* Computes the output state for a given node and input state.
*
* @param node The node.
* @param input Input lattice that should be read-only.
* @return Output lattice.
*/
abstract L flowThrough(N node, L input);
/**
* Finds a fixed-point solution using at most {@link #MAX_STEPS}
* iterations.
*
* @see #analyze(int)
*/
final void analyze() {
analyze(MAX_STEPS);
}
/**
* Finds a fixed-point solution. The function has the side effect of replacing
* the existing node annotations with the computed solutions using {@link
* com.google.javascript.jscomp.graph.GraphNode#setAnnotation(Annotation)}.
*
* <p>Initially, each node's input and output flow state contains the value
* given by {@link #createInitialEstimateLattice()} (with the exception of the
* entry node of the graph which takes on the {@link #createEntryLattice()}
* value. Each node will use the output state of its predecessor and compute a
* output state according to the instruction. At that time, any nodes that
* depends on the node's newly modified output value will need to recompute
* their output state again. Each step will perform a computation at one node
* until no extra computation will modify any existing output state anymore.
*
* @param maxSteps Max number of iterations before the method stops and throw
* a {@link MaxIterationsExceededException}. This will prevent the
* analysis from going into a infinite loop.
*/
final void analyze(int maxSteps) {
initialize();
int step = 0;
while (!orderedWorkSet.isEmpty()) {
if (step > maxSteps) {
throw new MaxIterationsExceededException(
"Analysis did not terminate after " + maxSteps + " iterations");
}
DiGraphNode<N, Branch> curNode = orderedWorkSet.iterator().next();
orderedWorkSet.remove(curNode);
joinInputs(curNode);
if (flow(curNode)) {
// If there is a change in the current node, we want to grab the list
// of nodes that this node affects.
List<DiGraphNode<N, Branch>> nextNodes = isForward() ?
cfg.getDirectedSuccNodes(curNode) :
cfg.getDirectedPredNodes(curNode);
for (DiGraphNode<N, Branch> nextNode : nextNodes) {
if (nextNode != cfg.getImplicitReturn()) {
orderedWorkSet.add(nextNode);
}
}
}
step++;
}
if (isForward()) {
joinInputs(getCfg().getImplicitReturn());
}
}
/**
* Gets the state of the initial estimation at each node.
*
* @return Initial state.
*/
abstract L createInitialEstimateLattice();
/**
* Gets the incoming state of the entry node.
*
* @return Entry state.
*/
abstract L createEntryLattice();
/**
* Initializes the work list and the control flow graph.
*/
protected void initialize() {
// TODO(user): Calling clear doesn't deallocate the memory in a
// LinkedHashSet. Consider creating a new work set if we plan to repeatedly
// call analyze.
orderedWorkSet.clear();
for (Di
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>GraphNode<N, Branch> node : cfg.getDirectedGraphNodes()) {
node.setAnnotation(new FlowState<L>(createInitialEstimateLattice(),
createInitialEstimateLattice()));
if (node != cfg.getImplicitReturn()) {
orderedWorkSet.add(node);
}
}
}
/**
* Performs a single flow through a node.
*
* @return {@code true} if the flow state differs from the previous state.
*/
protected boolean flow(DiGraphNode<N, Branch> node) {
FlowState<L> state = node.getAnnotation();
if (isForward()) {
L outBefore = state.out;
state.out = flowThrough(node.getValue(), state.in);
return !outBefore.equals(state.out);
} else {
L inBefore = state.in;
state.in = flowThrough(node.getValue(), state.out);
return !inBefore.equals(state.in);
}
}
/**
* Computes the new flow state at a given node's entry by merging the
* output (input) lattice of the node's predecessor (successor).
*
* @param node Node to compute new join.
*/
protected void joinInputs(DiGraphNode<N, Branch> node) {
FlowState<L> state = node.getAnnotation();
if (isForward()) {
if (cfg.getEntry() == node) {
state.setIn(createEntryLattice());
} else {
List<DiGraphNode<N, Branch>> inNodes = cfg.getDirectedPredNodes(node);
if (inNodes.size() == 1) {
FlowState<L> inNodeState = inNodes.get(0).getAnnotation();
state.setIn(inNodeState.getOut());
} else if (inNodes.size() > 1) {
List<L> values = new ArrayList<L>(inNodes.size());
for (DiGraphNode<N, Branch> currentNode : inNodes) {
FlowState<L> currentNodeState = currentNode.getAnnotation();
values.add(currentNodeState.getOut());
}
state.setIn(joinOp.apply(values));
}
}
} else {
List<DiGraphNode<N, Branch>> inNodes = cfg.getDirectedSuccNodes(node);
if (inNodes.size() == 1) {
DiGraphNode<N, Branch> inNode = inNodes.get(0);
if (inNode == cfg.getImplicitReturn()) {
state.setOut(createEntryLattice());
} else {
FlowState<L> inNodeState = inNode.getAnnotation();
state.setOut(inNodeState.getIn());
}
} else if (inNodes.size() > 1) {
List<L> values = new ArrayList<L>(inNodes.size());
for (DiGraphNode<N, Branch> currentNode : inNodes) {
FlowState<L> currentNodeState = currentNode.getAnnotation();
values.add(currentNodeState.getIn());
}
state.setOut(joinOp.apply(values));
}
}
}
/**
* The in and out states of a node.
*
* @param <L> Input and output lattice element type.
*/
static class FlowState<L extends LatticeElement> implements Annotation {
private L in;
private L out;
/**
* Private constructor. No other classes should create new states
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>.
*
* @param inState Input.
* @param outState Output.
*/
private FlowState(L inState, L outState) {
Preconditions.checkNotNull(inState);
Preconditions.checkNotNull(outState);
this.in = inState;
this.out = outState;
}
L getIn() {
return in;
}
void setIn(L in) {
Preconditions.checkNotNull(in);
this.in = in;
}
L getOut() {
return out;
}
void setOut(L out) {
Preconditions.checkNotNull(out);
this.out = out;
}
@Override
public String toString() {
return String.format("IN: %s OUT: %s", in, out);
}
@Override
public int hashCode() {
return Objects.hashCode(in, out);
}
}
/**
* The exception to be thrown if the analysis has been running for a long
* number of iterations. Chances are the analysis is not monotonic, a
* fixed-point cannot be found and it is currently stuck in an infinite loop.
*/
static class MaxIterationsExceededException extends RuntimeException {
private static final long serialVersionUID = 1L;
MaxIterationsExceededException(String msg) {
super(msg);
}
}
abstract static class BranchedForwardDataFlowAnalysis
<N, L extends LatticeElement> extends DataFlowAnalysis<N, L> {
@Override
protected void initialize() {
orderedWorkSet.clear();
for (DiGraphNode<N, Branch> node : getCfg().getDirectedGraphNodes()) {
int outEdgeCount = getCfg().getOutEdges(node.getValue()).size();
List<L> outLattices = Lists.newArrayList();
for (int i = 0; i < outEdgeCount; i++) {
outLattices.add(createInitialEstimateLattice());
}
node.setAnnotation(new BranchedFlowState<L>(
createInitialEstimateLattice(), outLattices));
if (node != getCfg().getImplicitReturn()) {
orderedWorkSet.add(node);
}
}
}
BranchedForwardDataFlowAnalysis(ControlFlowGraph<N> targetCfg,
JoinOp<L> joinOp) {
super(targetCfg, joinOp);
}
/**
* Returns the lattice element at the exit point. Needs to be overridden
* because we use a BranchedFlowState instead of a FlowState; ugh.
*/
@Override
L getExitLatticeElement() {
DiGraphNode<N, Branch> node = getCfg().getImplicitReturn();
BranchedFlowState<L> state = node.getAnnotation();
return state.getIn();
}
@Override
final boolean isForward() {
return true;
}
/**
* The branched flow function maps a single lattice to a list of output
* lattices.
*
* <p>Each outgoing edge of a node will have a corresponding output lattice
* in the ordered returned by
* {@link com.google.javascript.jscomp.graph.DiGraph#getOutEdges(Object)}
* in the returned list.
*
* @return A list of output values depending on the edge's branch type.
*/
abstract List<L> branchedFlowThrough(N node, L input);
@Override
protected final boolean flow(DiGraphNode<N, Branch> node) {
Branched
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>FlowState<L> state = node.getAnnotation();
List<L> outBefore = state.out;
state.out = branchedFlowThrough(node.getValue(), state.in);
Preconditions.checkState(outBefore.size() == state.out.size());
for (int i = 0; i < outBefore.size(); i++) {
if (!outBefore.get(i).equals(state.out.get(i))) {
return true;
}
}
return false;
}
@Override
protected void joinInputs(DiGraphNode<N, Branch> node) {
BranchedFlowState<L> state = node.getAnnotation();
List<DiGraphNode<N, Branch>> predNodes =
getCfg().getDirectedPredNodes(node);
List<L> values = new ArrayList<L>(predNodes.size());
for (DiGraphNode<N, Branch> predNode : predNodes) {
BranchedFlowState<L> predNodeState = predNode.getAnnotation();
L in = predNodeState.out.get(
getCfg().getDirectedSuccNodes(predNode).indexOf(node));
values.add(in);
}
if (getCfg().getEntry() == node) {
state.setIn(createEntryLattice());
} else if (!values.isEmpty()) {
state.setIn(joinOp.apply(values));
}
}
}
/**
* The in and out states of a node.
*
* @param <L> Input and output lattice element type.
*/
static class BranchedFlowState<L extends LatticeElement>
implements Annotation {
private L in;
private List<L> out;
/**
* Private constructor. No other classes should create new states.
*
* @param inState Input.
* @param outState Output.
*/
private BranchedFlowState(L inState, List<L> outState) {
Preconditions.checkNotNull(inState);
Preconditions.checkNotNull(outState);
this.in = inState;
this.out = outState;
}
L getIn() {
return in;
}
void setIn(L in) {
Preconditions.checkNotNull(in);
this.in = in;
}
List<L> getOut() {
return out;
}
void setOut(List<L> out) {
Preconditions.checkNotNull(out);
for (L item : out) {
Preconditions.checkNotNull(item);
}
this.out = out;
}
@Override
public String toString() {
return String.format("IN: %s OUT: %s", in, out);
}
@Override
public int hashCode() {
return Objects.hashCode(in, out);
}
}
/**
* Compute set of escaped variables. When a variable is escaped in a
* dataflow analysis, it can be reference outside of the code that we are
* analyzing. A variable is escaped if any of the following is true:
*
* <p><ol>
* <li>It is defined as the exception name in CATCH clause so it became a
* variable local not to our definition of scope.</li>
* <li>Exported variables as they can be needed after the script terminates.
* </li>
* <li>Names of named functions because in JavaScript, <i>function foo(){}</i>
* does not kill <i>foo</i> in the dataflow
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>_";
/**
* Pattern for unnamed messages.
* <p>
* All JS messages in JS code should have unique name but messages in
* generated code (i.e. from soy template) could have duplicated message names.
* Later we replace the message names with ids constructed as a hash of the
* message content.
* <p>
* <link href="http://code.google.com/p/closure-templates/">
* Soy</link> generates messages with names MSG_UNNAMED_<NUMBER> . This
* pattern recognizes such messages.
*/
private static final Pattern MSG_UNNAMED_PATTERN =
Pattern.compile("MSG_UNNAMED_\\d+");
private static final Pattern CAMELCASE_PATTERN =
Pattern.compile("[a-z][a-zA-Z\\d]*[_\\d]*");
static final String HIDDEN_DESC_PREFIX = "@hidden";
// For old-style JS messages
private static final String DESC_SUFFIX = "_HELP";
private final boolean needToCheckDuplications;
private final JsMessage.Style style;
private final JsMessage.IdGenerator idGenerator;
final AbstractCompiler compiler;
/**
* The names encountered associated with their defining node and source. We
* use it for tracking duplicated message ids in the source code.
*/
private final Map<String, MessageLocation> messageNames =
Maps.newHashMap();
/**
* List of found goog.getMsg call nodes.
*
* When we visit goog.getMsg() node we add a pair node:sourcename and later
* when we visit its parent we remove this pair. All nodes that are left at
* the end of traversing are orphaned nodes. It means have no corresponding
* var or property node.
*/
private final Map<Node, String> googMsgNodes = Maps.newHashMap();
private final CheckLevel checkLevel;
/**
* Creates JS message visitor.
*
* @param compiler the compiler instance
* @param needToCheckDuplications whether to check duplicated messages in
* traversed
* @param style style that should be used during parsing
* @param idGenerator generator that used for creating unique ID for the
* message
*/
JsMessageVisitor(AbstractCompiler compiler,
boolean needToCheckDuplications,
JsMessage.Style style, JsMessage.IdGenerator idGenerator) {
this.compiler = compiler;
this.needToCheckDuplications = needToCheckDuplications;
this.style = style;
this.idGenerator = idGenerator;
checkLevel = (style == JsMessage.Style.CLOSURE)
? CheckLevel.ERROR : CheckLevel.WARNING;
// TODO(anatol): add flag that decides whether to process UNNAMED messages.
// Some projects would not want such functionality (unnamed) as they don't
// use SOY templates.
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
for (Map.Entry<Node, String> msgNode : googMsgNodes.entrySet()) {
compiler.report(JSError.make(msgNode.getValue(), msgNode.getKey(),
checkLevel, MESSAGE_NODE_IS_ORPHANED));
}
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
String messageKey;
boolean isVar;
Node msgNode, msgNodeParent;
switch (node.getType()) {
case Token.NAME:
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> suggestion = "Consider casting " + qName +
" if you know it's type.";
}
} else {
List<String> errors = Lists.newArrayList();
printErrorLocations(errors, jsType);
if (!errors.isEmpty()) {
suggestion = "Consider fixing errors for the following types:\n";
suggestion += Joiner.on("\n").join(errors);
}
}
}
compiler.report(JSError.make(
t.getSourceName(), n, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()),
n.toString(), suggestion));
}
}
}
/**
* Processes a OBJECTLIT node.
*/
private void handleObjectLit(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
while (child != null) {
// Maybe STRING, GET, SET
// We should never see a mix of numbers and strings.
String name = child.getString();
T type = typeSystem.getType(getScope(), n, name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(child,
processProperty(t, prop, type, null))) {
// TODO(user): It doesn't look like the user can do much in this
// case right now.
if (propertiesToErrorFor.containsKey(name)) {
compiler.report(JSError.make(
t.getSourceName(), child, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()), n.toString(), ""));
}
}
child = child.getNext();
}
}
private void printErrorLocations(List<String> errors, JSType t) {
if (!t.isObject() || t.isAllType()) {
return;
}
if (t.isUnionType()) {
for (JSType alt : t.toMaybeUnionType().getAlternates()) {
printErrorLocations(errors, alt);
}
return;
}
for (JSError error : invalidationMap.get(t)) {
if (errors.size() > MAX_INVALDIATION_WARNINGS_PER_PROPERTY) {
return;
}
errors.add(
t.toString() + " at " + error.sourceName + ":" + error.lineNumber);
}
}
/**
* Processes a property, adding it to the list of properties to rename.
* @return a representative type for the property reference, which will be
* the highest type on the prototype chain of the provided type. In the
* case of a union type, it will be the highest type on the prototype
* chain of one of the members of the union.
*/
private T processProperty(
NodeTraversal t, Property prop, T type, T relatedType) {
type = typeSystem.restrictByNotNullOrUndefined(type);
if (prop.skipRenaming || typeSystem.isInvalidatingType(type)) {
return null;
}
Iterable<T> alternatives = typeSystem.getTypeAlternatives(type);
if (alternatives != null) {
T firstType = relatedType;
for (T subType : alternatives) {
T lastType = processProperty(t, prop, subType, firstType);
if (lastType != null) {
firstType = firstType == null ? lastType : firstType;
}
}
return
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> firstType;
} else {
T topType = typeSystem.getTypeWithProperty(prop.name, type);
if (typeSystem.isInvalidatingType(topType)) {
return null;
}
prop.addType(type, topType, relatedType);
return topType;
}
}
}
/** Renames all properties with references on more than one type. */
void renameProperties() {
int propsRenamed = 0, propsSkipped = 0, instancesRenamed = 0,
instancesSkipped = 0, singleTypeProps = 0;
for (Property prop : properties.values()) {
if (prop.shouldRename()) {
Map<T, String> propNames = buildPropNames(prop.getTypes(), prop.name);
++propsRenamed;
prop.expandTypesToSkip();
UnionFind<T> types = prop.getTypes();
for (Node node : prop.renameNodes) {
T rootType = prop.rootTypes.get(node);
if (prop.shouldRename(rootType)) {
String newName = propNames.get(rootType);
node.setString(newName);
compiler.reportCodeChange();
++instancesRenamed;
} else {
++instancesSkipped;
}
}
} else {
if (prop.skipRenaming) {
++propsSkipped;
} else {
++singleTypeProps;
}
}
}
logger.fine("Renamed " + instancesRenamed + " instances of "
+ propsRenamed + " properties.");
logger.fine("Skipped renaming " + instancesSkipped + " invalidated "
+ "properties, " + propsSkipped + " instances of properties "
+ "that were skipped for specific types and " + singleTypeProps
+ " properties that were referenced from only one type.");
}
/**
* Chooses a name to use for renaming in each equivalence class and maps
* each type in that class to it.
*/
private Map<T, String> buildPropNames(UnionFind<T> types, String name) {
Map<T, String> names = Maps.newHashMap();
for (Set<T> set : types.allEquivalenceClasses()) {
checkState(!set.isEmpty());
String typeName = null;
for (T type : set) {
if (typeName == null || type.toString().compareTo(typeName) < 0) {
typeName = type.toString();
}
}
String newName;
if ("{...}".equals(typeName)) {
newName = name;
} else {
newName = typeName.replaceAll("[^\\w$]", "_") + "$" + name;
}
for (T type : set) {
names.put(type, newName);
}
}
return names;
}
/** Returns a map from field name to types for which it will be renamed. */
Multimap<String, Collection<T>> getRenamedTypesForTesting() {
Multimap<String, Collection<T>> ret = HashMultimap.create();
for (Map.Entry<String, Property> entry: properties.entrySet()) {
Property prop = entry.getValue();
if (!prop.skipRenaming) {
for (Collection<T> c : prop.getTypes().allEquivalenceClasses()) {
if (!c.isEmpty() && !prop.typesToSkip.contains(c.iterator().next())) {
ret.put(entry.getKey(), c);
}
}
}
}
return ret;
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>),
registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
registry.getNativeType(JSTypeNative.OBJECT_PROTOTYPE),
registry.getNativeType(JSTypeNative.TOP_LEVEL_PROTOTYPE),
registry.getNativeType(JSTypeNative.UNKNOWN_TYPE));
}
@Override public void addInvalidatingType(JSType type) {
checkState(!type.isUnionType());
invalidatingTypes.add(type);
}
@Override public StaticScope<JSType> getRootScope() { return null; }
@Override public StaticScope<JSType> getFunctionScope(Node node) {
return null;
}
@Override public JSType getType(
StaticScope<JSType> scope, Node node, String prop) {
if (node.getJSType() == null) {
return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return node.getJSType();
}
@Override public boolean isInvalidatingType(JSType type) {
if (type == null || invalidatingTypes.contains(type) ||
type.isUnknownType() /* unresolved types */) {
return true;
}
ObjectType objType = ObjectType.cast(type);
return objType != null && !objType.hasReferenceName();
}
@Override public ImmutableSet<JSType> getTypesToSkipForType(JSType type) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
Set<JSType> types = Sets.newHashSet(type);
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
types.addAll(getTypesToSkipForTypeNonUnion(type));
}
return ImmutableSet.copyOf(types);
} else if (type.isEnumElementType()) {
return getTypesToSkipForType(
type.toMaybeEnumElementType().getPrimitiveType());
}
return ImmutableSet.copyOf(getTypesToSkipForTypeNonUnion(type));
}
private Set<JSType> getTypesToSkipForTypeNonUnion(JSType type) {
Set<JSType> types = Sets.newHashSet();
JSType skipType = type;
while (skipType != null) {
types.add(skipType);
ObjectType objSkipType = skipType.toObjectType();
if (objSkipType != null) {
skipType = objSkipType.getImplicitPrototype();
} else {
break;
}
}
return types;
}
@Override public boolean isTypeToSkip(JSType type) {
return type.isEnumType() || (type.autoboxesTo() != null);
}
@Override public JSType restrictByNotNullOrUndefined(JSType type) {
return type.restrictByNotNullOrUndefined();
}
@Override public Iterable<JSType> getTypeAlternatives(JSType type) {
if (type.isUnionType()) {
return type.toMaybeUnionType().getAlternates();
} else {
ObjectType objType = type.toObjectType();
if (objType != null &&
objType.getConstructor() != null &&
objType.getConstructor().isInterface()) {
List<JSType> list = Lists.newArrayList();
for (FunctionType impl
: registry.getDirectImplementors(objType)) {
list.add(impl.getInstanceType());
}
return list;
} else {
return null;
}
}
}
@Override public ObjectType getTypeWithProperty(
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>Stack) {
String newName = names.getReplacementName(oldName);
if (newName != null) {
return newName;
}
}
return null;
}
/**
* Traverses the current scope and collects declared names. Does not
* decent into functions or add CATCH exceptions.
*/
private void findDeclaredNames(Node n, Node parent, Renamer renamer) {
// Do a shallow traversal, so don't traverse into function declarations,
// except for the name of the function itself.
if (parent == null
|| !parent.isFunction()
|| n == parent.getFirstChild()) {
if (NodeUtil.isVarDeclaration(n)) {
renamer.addDeclaredName(n.getString());
} else if (NodeUtil.isFunctionDeclaration(n)) {
Node nameNode = n.getFirstChild();
renamer.addDeclaredName(nameNode.getString());
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
findDeclaredNames(c, n, renamer);
}
}
}
/**
* Declared names renaming policy interface.
*/
interface Renamer {
/**
* Called when a declared name is found in the local current scope.
*/
void addDeclaredName(String name);
/**
* @return A replacement name, null if oldName is unknown or should not
* be replaced.
*/
String getReplacementName(String oldName);
/**
* @return Whether the constant-ness of a name should be removed.
*/
boolean stripConstIfReplaced();
/**
* @return A Renamer for a scope within the scope of the current Renamer.
*/
Renamer forChildScope();
}
/**
* Inverts the transformation by {@link ContextualRenamer}, when possible.
*/
static class ContextualRenameInverter
implements ScopedCallback, CompilerPass {
private final AbstractCompiler compiler;
// The set of names referenced in the current scope.
private Set<String> referencedNames = ImmutableSet.of();
// Stack reference sets.
private Deque<Set<String>> referenceStack = new ArrayDeque<Set<String>>();
// Name are globally unique initially, so we don't need a per-scope map.
private Map<String, List<Node>> nameMap = Maps.newHashMap();
private ContextualRenameInverter(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node js) {
NodeTraversal.traverse(compiler, js, this);
}
public static String getOrginalName(String name) {
int index = indexOfSeparator(name);
return (index == -1) ? name : name.substring(0, index);
}
private static int indexOfSeparator(String name) {
return name.lastIndexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR);
}
private boolean containsSeparator(String name) {
return name.indexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR) != -1;
}
/**
* Prepare a set for the new scope.
*/
@Override
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return;
}
referenceStack.push(referencedNames);
referencedNames = Sets.newHashSet();
}
/**
* Rename vars for the current scope, and merge any referenced
* names into the parent scope reference set.
*/
@Override
public
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> void exitScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return;
}
for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) {
Var v = it.next();
handleScopeVar(v);
}
// Merge any names that were referenced but not declared in the current
// scope.
Set<String> current = referencedNames;
referencedNames = referenceStack.pop();
// If there isn't anything left in the stack we will be going into the
// global scope: don't try to build a set of referenced names for the
// global scope.
if (!referenceStack.isEmpty()) {
referencedNames.addAll(current);
}
}
/**
* For the Var declared in the current scope determine if it is possible
* to revert the name to its original form without conflicting with other
* values.
*/
void handleScopeVar(Var v) {
String name = v.getName();
if (containsSeparator(name) && !getOrginalName(name).isEmpty()) {
String newName = findReplacementName(name);
referencedNames.remove(name);
// Adding a reference to the new name to prevent either the parent
// scopes or the current scope renaming another var to this new name.
referencedNames.add(newName);
List<Node> references = nameMap.get(name);
Preconditions.checkState(references != null);
for (Node n : references) {
Preconditions.checkState(n.isName());
n.setString(newName);
}
compiler.reportCodeChange();
nameMap.remove(name);
}
}
/**
* Find a name usable in the local scope.
*/
private String findReplacementName(String name) {
String original = getOrginalName(name);
String newName = original;
int i = 0;
while (!isValidName(newName)) {
newName = original +
ContextualRenamer.UNIQUE_ID_SEPARATOR + String.valueOf(i++);
}
return newName;
}
/**
* @return Whether the name is valid to use in the local scope.
*/
private boolean isValidName(String name) {
if (TokenStream.isJSIdentifier(name) &&
!referencedNames.contains(name) &&
!name.equals(ARGUMENTS)) {
return true;
}
return false;
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
if (t.inGlobalScope()) {
return;
}
if (NodeUtil.isReferenceName(node)) {
String name = node.getString();
// Add all referenced names to the set so it is possible to check for
// conflicts.
referencedNames.add(name);
// Store only references to candidate names in the node map.
if (containsSeparator(name)) {
addCandidateNameReference(name, node);
}
}
}
private void addCandidateNameReference(String name, Node n) {
List<Node> nodes = nameMap.get(name);
if (null == nodes) {
nodes = Lists.newLinkedList();
nameMap.put(name, nodes);
}
nodes.add(n);
}
}
/**
* Rename every locally name to be unique, the first encountered declaration
* (specifically global names) are left in
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
PreprocessorSymbolTable getPreprocessorSymbolTable() {
return preprocessorSymbolTable;
}
void maybeInitializePreprocessorSymbolTable(AbstractCompiler compiler) {
if (options.ideMode) {
Node root = compiler.getRoot();
if (preprocessorSymbolTable == null ||
preprocessorSymbolTable.getRootNode() != root) {
preprocessorSymbolTable = new PreprocessorSymbolTable(root);
}
}
}
@Override
protected List<PassFactory> getChecks() {
List<PassFactory> checks = Lists.newArrayList();
checks.add(createEmptyPass("beforeStandardChecks"));
if (options.closurePass) {
checks.add(closureGoogScopeAliases);
}
if (options.nameAnonymousFunctionsOnly) {
if (options.anonymousFunctionNaming ==
AnonymousFunctionNamingPolicy.MAPPED) {
checks.add(nameMappedAnonymousFunctions);
} else if (options.anonymousFunctionNaming ==
AnonymousFunctionNamingPolicy.UNMAPPED) {
checks.add(nameUnmappedAnonymousFunctions);
}
return checks;
}
if (options.jqueryPass) {
checks.add(jqueryAliases.makeOneTimePass());
}
checks.add(checkSideEffects);
if (options.checkSuspiciousCode ||
options.enables(DiagnosticGroups.GLOBAL_THIS) ||
options.enables(DiagnosticGroups.DEBUGGER_STATEMENT_PRESENT)) {
checks.add(suspiciousCode);
}
if (options.checkControlStructures
|| options.enables(DiagnosticGroups.ES5_STRICT)) {
checks.add(checkControlStructures);
}
if (options.checkRequires.isOn()) {
checks.add(checkRequires);
}
if (options.checkProvides.isOn()) {
checks.add(checkProvides);
}
// The following passes are more like "preprocessor" passes.
// It's important that they run before most checking passes.
// Perhaps this method should be renamed?
if (options.generateExports) {
checks.add(generateExports);
}
if (options.exportTestFunctions) {
checks.add(exportTestFunctions);
}
if (options.closurePass) {
checks.add(closurePrimitives.makeOneTimePass());
}
if (options.closurePass && options.checkMissingGetCssNameLevel.isOn()) {
checks.add(closureCheckGetCssName);
}
if (options.syntheticBlockStartMarker != null) {
// This pass must run before the first fold constants pass.
checks.add(createSyntheticBlocks);
}
checks.add(checkVars);
if (options.computeFunctionSideEffects) {
checks.add(checkRegExp);
}
if (options.aggressiveVarCheck.isOn()) {
checks.add(checkVariableReferences);
}
// This pass should run before types are assigned.
if (options.processObjectPropertyString) {
checks.add(objectPropertyStringPreprocess);
}
if (options.checkTypes || options.inferTypes) {
checks.add(resolveTypes.makeOneTimePass());
checks.add(inferTypes.makeOneTimePass());
if (options.checkTypes) {
checks.add(checkTypes.makeOneTimePass());
} else {
checks.add(inferJsDocInfo.makeOneTimePass());
}
// We assume that only IDE-mode clients will try to query the
// typed scope creator after the compile job.
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> if (!options.ideMode && !options.saveDataStructures) {
checks.add(clearTypedScopePass.makeOneTimePass());
}
}
if (options.checkUnreachableCode.isOn() ||
(options.checkTypes && options.checkMissingReturn.isOn())) {
checks.add(checkControlFlow);
}
// CheckAccessControls only works if check types is on.
if (options.checkTypes &&
(options.enables(DiagnosticGroups.ACCESS_CONTROLS)
|| options.enables(DiagnosticGroups.CONSTANT_PROPERTY))) {
checks.add(checkAccessControls);
}
if (options.checkGlobalNamesLevel.isOn()) {
checks.add(checkGlobalNames);
}
if (options.enables(DiagnosticGroups.ES5_STRICT) || options.checkCaja) {
checks.add(checkStrictMode);
}
// Replace 'goog.getCssName' before processing defines but after the
// other checks have been done.
if (options.closurePass) {
checks.add(closureReplaceGetCssName);
}
// i18n
// If you want to customize the compiler to use a different i18n pass,
// you can create a PassConfig that calls replacePassFactory
// to replace this.
checks.add(options.messageBundle != null ?
replaceMessages : createEmptyPass("replaceMessages"));
if (options.getTweakProcessing().isOn()) {
checks.add(processTweaks);
}
// Defines in code always need to be processed.
checks.add(processDefines);
if (options.instrumentationTemplate != null ||
options.recordFunctionInformation) {
checks.add(computeFunctionNames);
}
if (options.nameReferenceGraphPath != null &&
!options.nameReferenceGraphPath.isEmpty()) {
checks.add(printNameReferenceGraph);
}
if (options.nameReferenceReportPath != null &&
!options.nameReferenceReportPath.isEmpty()) {
checks.add(printNameReferenceReport);
}
assertAllOneTimePasses(checks);
return checks;
}
@Override
protected List<PassFactory> getOptimizations() {
List<PassFactory> passes = Lists.newArrayList();
passes.add(garbageCollectChecks);
// TODO(nicksantos): The order of these passes makes no sense, and needs
// to be re-arranged.
if (options.runtimeTypeCheck) {
passes.add(runtimeTypeCheck);
}
passes.add(createEmptyPass("beforeStandardOptimizations"));
if (options.replaceIdGenerators) {
passes.add(replaceIdGenerators);
}
// Optimizes references to the arguments variable.
if (options.optimizeArgumentsArray) {
passes.add(optimizeArgumentsArray);
}
// Abstract method removal works best on minimally modified code, and also
// only needs to run once.
if (options.closurePass &&
(options.removeAbstractMethods || options.removeClosureAsserts)) {
passes.add(closureCodeRemoval);
}
// Collapsing properties can undo constant inlining, so we do this before
// the main optimization loop.
if (options.collapseProperties) {
passes.add(collapseProperties);
}
// ReplaceStrings runs after CollapseProperties in order to simplify
// pulling in values of constants defined in enums structures.
if (!options.replaceStringsFunctionDescriptions.isEmpty()) {
passes.add(replaceStrings);
}
//
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>Properties) {
passes.add(convertToDottedProperties);
}
// Property renaming must happen before this pass runs since this
// pass may convert dotted properties into quoted properties. It
// is beneficial to run before alias strings, alias keywords and
// variable renaming.
if (options.rewriteFunctionExpressions) {
passes.add(rewriteFunctionExpressions);
}
// This comes after converting quoted property accesses to dotted property
// accesses in order to avoid aliasing property names.
if (!options.aliasableStrings.isEmpty() || options.aliasAllStrings) {
passes.add(aliasStrings);
}
if (options.aliasExternals) {
passes.add(aliasExternals);
}
if (options.aliasKeywords) {
passes.add(aliasKeywords);
}
// Passes after this point can no longer depend on normalized AST
// assumptions.
passes.add(markUnnormalized);
if (options.coalesceVariableNames) {
passes.add(coalesceVariableNames);
// coalesceVariables creates identity assignments and more redundant code
// that can be removed, rerun the peephole optimizations to clean them
// up.
if (options.foldConstants) {
passes.add(peepholeOptimizations);
}
}
if (options.collapseVariableDeclarations) {
passes.add(exploitAssign);
passes.add(collapseVariableDeclarations);
}
// This pass works best after collapseVariableDeclarations.
passes.add(denormalize);
if (options.instrumentationTemplate != null) {
passes.add(instrumentFunctions);
}
if (options.variableRenaming != VariableRenamingPolicy.ALL) {
// If we're leaving some (or all) variables with their old names,
// then we need to undo any of the markers we added for distinguishing
// local variables ("$$1").
passes.add(invertContextualRenaming);
}
if (options.variableRenaming != VariableRenamingPolicy.OFF) {
passes.add(renameVars);
}
if (options.groupVariableDeclarations) {
passes.add(groupVariableDeclarations);
}
// This pass should run after names stop changing.
if (options.processObjectPropertyString) {
passes.add(objectPropertyStringPostprocess);
}
if (options.labelRenaming) {
passes.add(renameLabels);
}
if (options.foldConstants) {
passes.add(latePeepholeOptimizations);
}
if (options.anonymousFunctionNaming ==
AnonymousFunctionNamingPolicy.UNMAPPED) {
passes.add(nameUnmappedAnonymousFunctions);
}
if (options.renamePrefixNamespace != null) {
if (!GLOBAL_SYMBOL_NAMESPACE_PATTERN.matcher(
options.renamePrefixNamespace).matches()) {
throw new IllegalArgumentException(
"Illegal character in renamePrefixNamespace name: "
+ options.renamePrefixNamespace);
}
passes.add(rescopeGlobalSymbols);
}
passes.add(stripSideEffectProtection);
// Safety checks
passes.add(sanityCheckAst);
passes.add(sanityCheckVars);
return passes;
}
/** Creates the passes for the main optimization loop. */
private List<PassFactory> getMainOptimizationLoop() {
List<PassFactory> passes = Lists.newArrayList();
if (options.inlineGetters) {
passes.add(inlineSimpleMethods);
}
passes.addAll(getCodeRemovingPasses());
if (options.inlineFunctions || options.
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>inlineLocalFunctions) {
passes.add(inlineFunctions);
}
if (options.inlineProperties) {
passes.add(inlineProperties);
}
boolean runOptimizeCalls = options.optimizeCalls
|| options.optimizeParameters
|| options.optimizeReturns;
if (options.removeUnusedVars || options.removeUnusedLocalVars) {
if (options.deadAssignmentElimination) {
passes.add(deadAssignmentsElimination);
}
if (!runOptimizeCalls) {
passes.add(removeUnusedVars);
}
}
if (runOptimizeCalls) {
passes.add(optimizeCallsAndRemoveUnusedVars);
}
assertAllLoopablePasses(passes);
return passes;
}
/** Creates several passes aimed at removing code. */
private List<PassFactory> getCodeRemovingPasses() {
List<PassFactory> passes = Lists.newArrayList();
if (options.collapseObjectLiterals && !isInliningForbidden()) {
passes.add(collapseObjectLiterals);
}
if (options.inlineVariables || options.inlineLocalVariables) {
passes.add(inlineVariables);
} else if (options.inlineConstantVars) {
passes.add(inlineConstants);
}
if (options.foldConstants) {
// These used to be one pass.
passes.add(minimizeExitPoints);
passes.add(peepholeOptimizations);
}
if (options.removeDeadCode) {
passes.add(removeUnreachableCode);
}
if (options.removeUnusedPrototypeProperties) {
passes.add(removeUnusedPrototypeProperties);
}
if (options.removeUnusedClassProperties && !isInliningForbidden()) {
passes.add(removeUnusedClassProperties);
}
assertAllLoopablePasses(passes);
return passes;
}
/**
* Checks for code that is probably wrong (such as stray expressions).
*/
final HotSwapPassFactory checkSideEffects =
new HotSwapPassFactory("checkSideEffects", true) {
@Override
protected HotSwapCompilerPass createInternal(final AbstractCompiler
compiler) {
// The current approach to protecting "hidden" side-effects is to
// wrap them in a function call that is stripped later, this shouldn't
// be done in IDE mode where AST changes may be unexpected.
boolean protectHiddenSideEffects =
options.protectHiddenSideEffects && !options.ideMode;
return new CheckSideEffects(compiler,
options.checkSuspiciousCode ? CheckLevel.WARNING : CheckLevel.OFF,
protectHiddenSideEffects);
}
};
/**
* Checks for code that is probably wrong (such as stray expressions).
*/
final PassFactory stripSideEffectProtection =
new PassFactory("stripSideEffectProtection", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler
compiler) {
return new CheckSideEffects.StripProtection(compiler);
}
};
/**
* Checks for code that is probably wrong (such as stray expressions).
*/
// TODO(bolinfest): Write a CompilerPass for this.
final HotSwapPassFactory suspiciousCode =
new HotSwapPassFactory("suspiciousCode", true) {
@Override
protected HotSwapCompilerPass createInternal(final AbstractCompiler
compiler) {
List<Callback> sharedCallbacks = Lists.newArrayList();
if (options.checkSuspiciousCode) {
sharedCallbacks.add(new CheckAccidentalSemicolon(CheckLevel.WARNING));
}
if (options.enables(DiagnosticGroups
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>.GLOBAL_THIS)) {
sharedCallbacks.add(new CheckGlobalThis(compiler));
}
if (options.enables(DiagnosticGroups.DEBUGGER_STATEMENT_PRESENT)) {
sharedCallbacks.add(new CheckDebuggerStatement(compiler));
}
return combineChecks(compiler, sharedCallbacks);
}
};
/** Verify that all the passes are one-time passes. */
private void assertAllOneTimePasses(List<PassFactory> passes) {
for (PassFactory pass : passes) {
Preconditions.checkState(pass.isOneTimePass());
}
}
/** Verify that all the passes are multi-run passes. */
private void assertAllLoopablePasses(List<PassFactory> passes) {
for (PassFactory pass : passes) {
Preconditions.checkState(!pass.isOneTimePass());
}
}
/** Checks for validity of the control structures. */
final HotSwapPassFactory checkControlStructures =
new HotSwapPassFactory("checkControlStructures", true) {
@Override
protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) {
return new ControlStructureCheck(compiler);
}
};
/** Checks that all constructed classes are goog.require()d. */
final HotSwapPassFactory checkRequires =
new HotSwapPassFactory("checkRequires", true) {
@Override
protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) {
return new CheckRequiresForConstructors(compiler, options.checkRequires);
}
};
/** Makes sure @constructor is paired with goog.provides(). */
final HotSwapPassFactory checkProvides =
new HotSwapPassFactory("checkProvides", true) {
@Override
protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) {
return new CheckProvides(compiler, options.checkProvides);
}
};
private static final DiagnosticType GENERATE_EXPORTS_ERROR =
DiagnosticType.error(
"JSC_GENERATE_EXPORTS_ERROR",
"Exports can only be generated if export symbol/property " +
"functions are set.");
/** Generates exports for @export annotations. */
final PassFactory generateExports =
new PassFactory("generateExports", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
CodingConvention convention = compiler.getCodingConvention();
if (convention.getExportSymbolFunction() != null &&
convention.getExportPropertyFunction() != null) {
return new GenerateExports(compiler,
convention.getExportSymbolFunction(),
convention.getExportPropertyFunction());
} else {
return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR);
}
}
};
/** Generates exports for functions associated with JsUnit. */
final PassFactory exportTestFunctions =
new PassFactory("exportTestFunctions", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
CodingConvention convention = compiler.getCodingConvention();
if (convention.getExportSymbolFunction() != null) {
return new ExportTestFunctions(compiler,
convention.getExportSymbolFunction(),
convention.getExportPropertyFunction());
} else {
return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR);
}
}
};
/** Raw exports processing pass. */
final PassFactory gatherRawExports =
new PassFactory("gatherRawExports", false) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
final GatherRawExports pass = new GatherRawExports(
compiler);
return new CompilerPass() {
@Override
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
final HotSwapPassFactory resolveTypes =
new HotSwapPassFactory("resolveTypes", false) {
@Override
protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) {
return new GlobalTypeResolver(compiler);
}
};
/** Clears the typed scope when we're done. */
final PassFactory clearTypedScopePass =
new PassFactory("clearTypedScopePass", false) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new ClearTypedScope();
}
};
/** Runs type inference. */
final HotSwapPassFactory inferTypes =
new HotSwapPassFactory("inferTypes", false) {
@Override
protected HotSwapCompilerPass createInternal(final AbstractCompiler
compiler) {
return new HotSwapCompilerPass() {
@Override
public void process(Node externs, Node root) {
Preconditions.checkNotNull(topScope);
Preconditions.checkNotNull(getTypedScopeCreator());
makeTypeInference(compiler).process(externs, root);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
makeTypeInference(compiler).inferTypes(scriptRoot);
}
};
}
};
final HotSwapPassFactory inferJsDocInfo =
new HotSwapPassFactory("inferJsDocInfo", false) {
@Override
protected HotSwapCompilerPass createInternal(
final AbstractCompiler compiler) {
return new HotSwapCompilerPass() {
@Override
public void process(Node externs, Node root) {
Preconditions.checkNotNull(topScope);
Preconditions.checkNotNull(getTypedScopeCreator());
makeInferJsDocInfo(compiler).process(externs, root);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
makeInferJsDocInfo(compiler).hotSwapScript(scriptRoot, originalRoot);
}
};
}
};
/** Checks type usage */
final HotSwapPassFactory checkTypes =
new HotSwapPassFactory("checkTypes", false) {
@Override
protected HotSwapCompilerPass createInternal(final AbstractCompiler
compiler) {
return new HotSwapCompilerPass() {
@Override
public void process(Node externs, Node root) {
Preconditions.checkNotNull(topScope);
Preconditions.checkNotNull(getTypedScopeCreator());
TypeCheck check = makeTypeCheck(compiler);
check.process(externs, root);
compiler.getErrorManager().setTypedPercent(check.getTypedPercent());
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
makeTypeCheck(compiler).check(scriptRoot, false);
}
};
}
};
/**
* Checks possible execution paths of the program for problems: missing return
* statements and dead code.
*/
final HotSwapPassFactory checkControlFlow =
new HotSwapPassFactory("checkControlFlow", true) {
@Override
protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) {
List<Callback> callbacks = Lists.newArrayList();
if (options.checkUnreachableCode.isOn()) {
callbacks.add(
new CheckUnreachableCode(compiler, options.checkUnreachableCode));
}
if (options.checkMissingReturn.isOn() && options.checkTypes) {
callbacks.add(
new CheckMissingReturn(compiler, options.checkMissingReturn));
}
return combineChecks(compiler, callbacks);
}
};
/** Checks access controls. Depends on type-inference. */
final HotSwapPassFactory checkAccessControls =
new
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> HotSwapPassFactory("checkAccessControls", true) {
@Override
protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) {
return new CheckAccessControls(compiler);
}
};
/** Executes the given callbacks with a {@link CombinedCompilerPass}. */
private static HotSwapCompilerPass combineChecks(AbstractCompiler compiler,
List<Callback> callbacks) {
Preconditions.checkArgument(callbacks.size() > 0);
Callback[] array = callbacks.toArray(new Callback[callbacks.size()]);
return new CombinedCompilerPass(compiler, array);
}
/** A compiler pass that resolves types in the global scope. */
class GlobalTypeResolver implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
GlobalTypeResolver(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
if (topScope == null) {
regenerateGlobalTypedScope(compiler, root.getParent());
} else {
compiler.getTypeRegistry().resolveTypesInScope(topScope);
}
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
patchGlobalTypedScope(compiler, scriptRoot);
}
}
/** A compiler pass that clears the global scope. */
class ClearTypedScope implements CompilerPass {
@Override
public void process(Node externs, Node root) {
clearTypedScope();
}
}
/** Checks global name usage. */
final PassFactory checkGlobalNames =
new PassFactory("checkGlobalNames", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node jsRoot) {
// Create a global namespace for analysis by check passes.
// Note that this class does all heavy computation lazily,
// so it's OK to create it here.
namespaceForChecks = new GlobalNamespace(compiler, externs, jsRoot);
new CheckGlobalNames(compiler, options.checkGlobalNamesLevel)
.injectNamespace(namespaceForChecks).process(externs, jsRoot);
}
};
}
};
/** Checks that the code is ES5 or Caja compliant. */
final PassFactory checkStrictMode =
new PassFactory("checkStrictMode", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new StrictModeCheck(compiler,
!options.checkSymbols, // don't check variables twice
!options.checkCaja); // disable eval check if not Caja
}
};
/** Process goog.tweak.getTweak() calls. */
final PassFactory processTweaks = new PassFactory("processTweaks", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node jsRoot) {
new ProcessTweaks(compiler,
options.getTweakProcessing().shouldStrip(),
options.getTweakReplacements()).process(externs, jsRoot);
}
};
}
};
/** Override @define-annotated constants. */
final PassFactory processDefines =
new PassFactory("processDefines", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node jsRoot) {
Map<String, Node> replacements = getAdditionalReplacements(options);
replacement
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>able.
*
* @param name A global variable or function name.
* @param local {@code true} if the name is a local variable.
* @return {@code true} if the name should be considered exported.
*/
public boolean isExported(String name, boolean local);
/**
* Should be isExported(name, true) || isExported(name, false);
*/
public boolean isExported(String name);
/**
* Checks whether a name should be considered private. Private global
* variables and functions can only be referenced within the source file in
* which they are declared. Private properties and methods should only be
* accessed by the class that defines them.
*
* @param name The name of a global variable or function, or a method or
* property.
* @return {@code true} if the name should be considered private.
*/
public boolean isPrivate(String name);
/**
* Checks if the given method defines a subclass relationship,
* and if it does, returns information on that relationship. By default,
* always returns null. Meant to be overridden by subclasses.
*
* @param callNode A CALL node.
*/
public SubclassRelationship getClassesDefinedByCall(Node callNode);
/**
* Returns true if passed a string referring to the superclass. The string
* will usually be from the string node at the right of a GETPROP, e.g.
* this.superClass_.
*/
public boolean isSuperClassReference(String propertyName);
/**
* Convenience method for determining provided dependencies amongst different
* JS scripts.
*/
public String extractClassNameIfProvide(Node node, Node parent);
/**
* Convenience method for determining required dependencies amongst different
* JS scripts.
*/
public String extractClassNameIfRequire(Node node, Node parent);
/**
* Function name used when exporting properties.
* Signature: fn(object, publicName, symbol).
* @return function name.
*/
public String getExportPropertyFunction();
/**
* Function name used when exporting symbols.
* Signature: fn(publicPath, object).
* @return function name.
*/
public String getExportSymbolFunction();
/**
* Checks if the given CALL node is forward-declaring any types,
* and returns the name of the types if it is.
*/
public List<String> identifyTypeDeclarationCall(Node n);
/**
* In many JS libraries, the function that produces inheritance also
* adds properties to the superclass and/or subclass.
*/
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type);
/**
* Function name for abstract methods. An abstract method can be assigned to
* an interface method instead of an function expression in order to avoid
* linter warnings produced by assigning a function without a return value
* where a return value is expected.
* @return function name.
*/
public String getAbstractMethodName();
/**
* Checks if the given method defines a singleton getter, and if it does,
* returns the name of the class with the singleton getter. By default, always
* returns null. Meant to be overridden by subclasses.
*
* @param callNode A CALL node.
*/
public String getSingletonGetterClassName(Node callNode);
/**
* In many JS libraries, the function that adds a singleton getter to a class
* adds properties to the class.
*/
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType);
/**
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> * @return Whether the function is inlinable by convention.
*/
public boolean isInlinableFunction(Node n);
/**
* @return the delegate relationship created by the call or null.
*/
public DelegateRelationship getDelegateRelationship(Node callNode);
/**
* In many JS libraries, the function that creates a delegate relationship
* also adds properties to the delegator and delegate base.
*/
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate);
/**
* @return the name of the delegate superclass.
*/
public String getDelegateSuperclassName();
/**
* Checks for function calls that set the calling conventions on delegate
* methods.
*/
public void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions);
/**
* Defines the delegate proxy prototype properties. Their types depend on
* properties of the delegate base methods.
*
* @param delegateProxyPrototypes List of delegate proxy prototypes.
*/
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions);
/**
* Gets the name of the global object.
*/
public String getGlobalObject();
/**
* A Bind instance or null.
*/
public Bind describeFunctionBind(Node n);
/**
* A Bind instance or null.
* @param useTypeInfo If we believe type information is reliable enough
* to use to figure out what the bind function is.
*/
public Bind describeFunctionBind(Node n, boolean useTypeInfo);
public static class Bind {
// The target of the bind action
final Node target;
// The node representing the "this" value, maybe null
final Node thisValue;
// The head of a Node list representing the parameters
final Node parameters;
public Bind(Node target, Node thisValue, Node parameters) {
this.target = target;
this.thisValue = thisValue;
this.parameters = parameters;
}
/**
* The number of parameters bound (not including the 'this' value).
*/
int getBoundParameterCount() {
if (parameters == null) {
return 0;
}
Node paramParent = parameters.getParent();
return paramParent.getChildCount() -
paramParent.getIndexOfChild(parameters);
}
}
/**
* Whether this CALL function is testing for the existence of a property.
*/
public boolean isPropertyTestFunction(Node call);
/**
* Whether this GETPROP node is an alias for an object prototype.
*/
public boolean isPrototypeAlias(Node getProp);
/**
* Checks if the given method performs a object literal cast, and if it does,
* returns information on the cast. By default, always returns null. Meant
* to be overridden by subclasses.
*
* @param callNode A CALL node.
*/
public ObjectLiteralCast getObjectLiteralCast(Node callNode);
/**
* Gets a collection of all properties that are defined indirectly on global
* objects. (For example, Closure defines superClass_ in the goog.inherits
* call).
*/
public Collection<String> getIndirectlyDeclaredProperties();
/**
* Returns the set of AssertionFunction.
*/
public Collection<AssertionFunctionSpec> getAssertionFunctions();
static enum SubclassType {
INHERITS,
MIXIN
}
static class
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>;
}
return this;
}
@Override
public String toDebugHashCodeString() {
List<String> hashCodes = Lists.newArrayList();
for (JSType a : alternates) {
hashCodes.add(a.toDebugHashCodeString());
}
return "{(" + Joiner.on(",").join(hashCodes) + ")}";
}
@Override
public boolean setValidator(Predicate<JSType> validator) {
for (JSType a : alternates) {
a.setValidator(validator);
}
return true;
}
@Override
public JSType collapseUnion() {
JSType currentValue = null;
ObjectType currentCommonSuper = null;
for (JSType a : alternates) {
if (a.isUnknownType()) {
return getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
ObjectType obj = a.toObjectType();
if (obj == null) {
if (currentValue == null && currentCommonSuper == null) {
// If obj is not an object, then it must be a value.
currentValue = a;
} else {
// Multiple values and objects will always collapse to the ALL_TYPE.
return getNativeType(JSTypeNative.ALL_TYPE);
}
} else if (currentValue != null) {
// Values and objects will always collapse to the ALL_TYPE.
return getNativeType(JSTypeNative.ALL_TYPE);
} else if (currentCommonSuper == null) {
currentCommonSuper = obj;
} else {
currentCommonSuper =
registry.findCommonSuperObject(currentCommonSuper, obj);
}
}
return currentCommonSuper;
}
@Override
public void matchConstraint(JSType constraint) {
for (JSType alternate : alternates) {
alternate.matchConstraint(constraint);
}
}
@Override
public boolean hasAnyTemplateInternal() {
for (JSType alternate : alternates) {
if (alternate.hasAnyTemplate()) {
return true;
}
}
return false;
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> charno) {
errorReporter.warning(
"Bad type annotation. " +
ScriptRuntime.getMessage0(messageId),
getSourceName(), lineno, null, charno);
}
}
// The DocInfo with the fileoverview tag for the whole file.
private JSDocInfo fileOverviewJSDocInfo = null;
private State state;
private final Map<String, Annotation> annotationNames;
private final Set<String> suppressionNames;
static private final Set<String> modifiesAnnotationKeywords =
ImmutableSet.<String>of("this", "arguments");
private Node.FileLevelJsDocBuilder fileLevelJsDocBuilder;
/**
* Sets the JsDocBuilder for the file-level (root) node of this parse. The
* parser uses the builder to append any preserve annotations it encounters
* in JsDoc comments.
*
* @param fileLevelJsDocBuilder
*/
void setFileLevelJsDocBuilder(
Node.FileLevelJsDocBuilder fileLevelJsDocBuilder) {
this.fileLevelJsDocBuilder = fileLevelJsDocBuilder;
}
/**
* Sets the file overview JSDocInfo, in order to warn about multiple uses of
* the @fileoverview tag in a file.
*/
void setFileOverviewJSDocInfo(JSDocInfo fileOverviewJSDocInfo) {
this.fileOverviewJSDocInfo = fileOverviewJSDocInfo;
}
private enum State {
SEARCHING_ANNOTATION,
SEARCHING_NEWLINE,
NEXT_IS_ANNOTATION
}
JsDocInfoParser(JsDocTokenStream stream,
Comment commentNode,
Node associatedNode,
Config config,
ErrorReporter errorReporter) {
this.stream = stream;
this.associatedNode = associatedNode;
// Sometimes this will be null in tests.
this.sourceFile = associatedNode == null
? null : associatedNode.getStaticSourceFile();
this.jsdocBuilder = new JSDocInfoBuilder(config.parseJsDocDocumentation);
if (commentNode != null) {
this.jsdocBuilder.recordOriginalCommentString(commentNode.getValue());
}
this.annotationNames = config.annotationNames;
this.suppressionNames = config.suppressionNames;
this.errorReporter = errorReporter;
this.templateNode = this.createTemplateNode();
}
private String getSourceName() {
return sourceFile == null ? null : sourceFile.getName();
}
/**
* Parses a string containing a JsDoc type declaration, returning the
* type if the parsing succeeded or {@code null} if it failed.
*/
public static Node parseTypeString(String typeString) {
Config config = new Config(
Sets.<String>newHashSet(),
Sets.<String>newHashSet(),
false,
LanguageMode.ECMASCRIPT3,
false);
JsDocInfoParser parser = new JsDocInfoParser(
new JsDocTokenStream(typeString),
null,
null,
config,
NullErrorReporter.forNewRhino());
return parser.parseTopLevelTypeExpression(parser.next());
}
/**
* Parses a {@link JSDocInfo} object. This parsing method reads all tokens
* returned by the {@link JsDocTokenStream#getJsDocToken()} method until the
* {@link JsDocToken#EOC} is returned.
*
* @return {@code true} if JSDoc information was correctly parsed,
* {@code false} otherwise
*/
boolean parse
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>() {
int lineno;
int charno;
// JSTypes are represented as Rhino AST nodes, and then resolved later.
JSTypeExpression type;
state = State.SEARCHING_ANNOTATION;
skipEOLs();
JsDocToken token = next();
List<ExtendedTypeInfo> extendedTypes = Lists.newArrayList();
// Always record that we have a comment.
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo blockInfo = extractBlockComment(token);
token = blockInfo.token;
if (!blockInfo.string.isEmpty()) {
jsdocBuilder.recordBlockDescription(blockInfo.string);
}
} else {
if (token != JsDocToken.ANNOTATION &&
token != JsDocToken.EOC) {
// Mark that there was a description, but don't bother marking
// what it was.
jsdocBuilder.recordBlockDescription("");
}
}
// Parse the actual JsDoc.
retry: for (;;) {
switch (token) {
case ANNOTATION:
if (state == State.SEARCHING_ANNOTATION) {
state = State.SEARCHING_NEWLINE;
lineno = stream.getLineno();
charno = stream.getCharno();
String annotationName = stream.getString();
Annotation annotation = annotationNames.get(annotationName);
if (annotation == null) {
parser.addParserWarning("msg.bad.jsdoc.tag", annotationName,
stream.getLineno(), stream.getCharno());
} else {
// Mark the beginning of the annotation.
jsdocBuilder.markAnnotation(annotationName, lineno, charno);
switch (annotation) {
case AUTHOR:
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo authorInfo = extractSingleLineBlock();
String author = authorInfo.string;
if (author.length() == 0) {
parser.addParserWarning("msg.jsdoc.authormissing",
stream.getLineno(), stream.getCharno());
} else {
jsdocBuilder.addAuthor(author);
}
token = authorInfo.token;
} else {
token = eatTokensUntilEOL(token);
}
continue retry;
case CONSISTENTIDGENERATOR:
if (!jsdocBuilder.recordConsistentIdGenerator()) {
parser.addParserWarning("msg.jsdoc.consistidgen",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case CONSTANT:
if (!jsdocBuilder.recordConstancy()) {
parser.addParserWarning("msg.jsdoc.const",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case CONSTRUCTOR:
if (!jsdocBuilder.recordConstructor()) {
if (jsdocBuilder.isInterfaceRecorded()) {
parser.addTypeWarning("msg.jsdoc.interface.constructor",
stream.getLineno(), stream.getCharno());
} else {
parser.addTypeWarning("msg.jsdoc.incompat.type",
stream.getLineno(), stream.getCharno());
}
}
token = eatTokensUntilEOL();
continue retry;
case DEPRECATED:
if (!jsdocBuilder.recordDeprecated()) {
parser.addParserWarning("msg.jsdoc.deprecated",
stream.getLineno(), stream.getCharno());
}
// Find
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>("msg.jsdoc.visibility.public",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case NO_SHADOW:
if (!jsdocBuilder.recordNoShadow()) {
parser.addParserWarning("msg.jsdoc.noshadow",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case NO_SIDE_EFFECTS:
if (!jsdocBuilder.recordNoSideEffects()) {
parser.addParserWarning("msg.jsdoc.nosideeffects",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case MODIFIES:
token = parseModifiesTag(next());
continue retry;
case IMPLICIT_CAST:
if (!jsdocBuilder.recordImplicitCast()) {
parser.addTypeWarning("msg.jsdoc.implicitcast",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case SEE:
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo referenceInfo = extractSingleLineBlock();
String reference = referenceInfo.string;
if (reference.length() == 0) {
parser.addParserWarning("msg.jsdoc.seemissing",
stream.getLineno(), stream.getCharno());
} else {
jsdocBuilder.addReference(reference);
}
token = referenceInfo.token;
} else {
token = eatTokensUntilEOL(token);
}
continue retry;
case SUPPRESS:
token = parseSuppressTag(next());
continue retry;
case TEMPLATE:
ExtractionInfo templateInfo = extractSingleLineBlock();
List<String> names = Lists.newArrayList(
Splitter.on(',')
.trimResults()
.split(templateInfo.string));
if (names.size() == 0 || names.get(0).length() == 0) {
parser.addTypeWarning("msg.jsdoc.templatemissing",
stream.getLineno(), stream.getCharno());
} else if (!jsdocBuilder.recordTemplateTypeNames(names)) {
parser.addTypeWarning("msg.jsdoc.template.at.most.once",
stream.getLineno(), stream.getCharno());
}
token = templateInfo.token;
continue retry;
case IDGENERATOR:
if (!jsdocBuilder.recordIdGenerator()) {
parser.addParserWarning("msg.jsdoc.idgen",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case VERSION:
ExtractionInfo versionInfo = extractSingleLineBlock();
String version = versionInfo.string;
if (version.length() == 0) {
parser.addParserWarning("msg.jsdoc.versionmissing",
stream.getLineno(), stream.getCharno());
} else {
if (!jsdocBuilder.recordVersion(version)) {
parser.addParserWarning("msg.jsdoc.extraversion",
stream.getLineno(), stream.getCharno());
}
}
token = versionInfo.token;
continue retry;
case DEFINE:
case RETURN:
case THIS:
case TYPE:
case TYPEDEF:
lineno = stream.getLineno();
charno = stream.getCharno
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>();
}
}
private void checkExtendedTypes(List<ExtendedTypeInfo> extendedTypes) {
for (ExtendedTypeInfo typeInfo : extendedTypes) {
// If interface, record the multiple extended interfaces
if (jsdocBuilder.isInterfaceRecorded()) {
if (!jsdocBuilder.recordExtendedInterface(typeInfo.type)) {
parser.addParserWarning("msg.jsdoc.extends.duplicate",
typeInfo.lineno, typeInfo.charno);
}
} else {
if (!jsdocBuilder.recordBaseType(typeInfo.type)) {
parser.addTypeWarning("msg.jsdoc.incompat.type",
typeInfo.lineno, typeInfo.charno);
}
}
}
}
/**
* Parse a {@code @suppress} tag of the form
* {@code @suppress{warning1|warning2}}.
*
* @param token The current token.
*/
private JsDocToken parseSuppressTag(JsDocToken token) {
if (token == JsDocToken.LC) {
Set<String> suppressions = new HashSet<String>();
while (true) {
if (match(JsDocToken.STRING)) {
String name = stream.getString();
if (!suppressionNames.contains(name)) {
parser.addParserWarning("msg.jsdoc.suppress.unknown", name,
stream.getLineno(), stream.getCharno());
}
suppressions.add(stream.getString());
token = next();
} else {
parser.addParserWarning("msg.jsdoc.suppress",
stream.getLineno(), stream.getCharno());
return token;
}
if (match(JsDocToken.PIPE)) {
token = next();
} else {
break;
}
}
if (!match(JsDocToken.RC)) {
parser.addParserWarning("msg.jsdoc.suppress",
stream.getLineno(), stream.getCharno());
} else {
token = next();
if (!jsdocBuilder.recordSuppressions(suppressions)) {
parser.addParserWarning("msg.jsdoc.suppress.duplicate",
stream.getLineno(), stream.getCharno());
}
}
}
return token;
}
/**
* Parse a {@code @modifies} tag of the form
* {@code @modifies{this|arguments|param}}.
*
* @param token The current token.
*/
private JsDocToken parseModifiesTag(JsDocToken token) {
if (token == JsDocToken.LC) {
Set<String> modifies = new HashSet<String>();
while (true) {
if (match(JsDocToken.STRING)) {
String name = stream.getString();
if (!modifiesAnnotationKeywords.contains(name)
&& !jsdocBuilder.hasParameter(name)) {
parser.addParserWarning("msg.jsdoc.modifies.unknown", name,
stream.getLineno(), stream.getCharno());
}
modifies.add(stream.getString());
token = next();
} else {
parser.addParserWarning("msg.jsdoc.modifies",
stream.getLineno(), stream.getCharno());
return token;
}
if (match(JsDocToken.PIPE)) {
token = next();
} else {
break;
}
}
if (!match(JsDocToken.RC))
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> return true;
default:
return false;
}
}
/**
* Get the value of the @nosideeffects annotation stored in the
* doc info.
*/
private static boolean hasNoSideEffectsAnnotation(Node node) {
JSDocInfo docInfo = node.getJSDocInfo();
return docInfo != null && docInfo.isNoSideEffects();
}
/**
* Gather function nodes that have @nosideeffects annotations.
*/
private class GatherNoSideEffectFunctions extends AbstractPostOrderCallback {
private final boolean inExterns;
GatherNoSideEffectFunctions(boolean inExterns) {
this.inExterns = inExterns;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
if (!inExterns && hasNoSideEffectsAnnotation(node)) {
traversal.report(node, INVALID_NO_SIDE_EFFECT_ANNOTATION);
}
if (node.isGetProp()) {
if (parent.isExprResult() &&
hasNoSideEffectsAnnotation(node)) {
noSideEffectFunctionNames.add(node);
}
} else if (node.isFunction()) {
// The annotation may attached to the function node, the
// variable declaration or assignment expression.
boolean hasAnnotation = hasNoSideEffectsAnnotation(node);
List<Node> nameNodes = Lists.newArrayList();
nameNodes.add(node.getFirstChild());
Node nameNode = null;
if (parent.isName()) {
Node gramp = parent.getParent();
if (gramp.isVar() &&
gramp.hasOneChild() &&
hasNoSideEffectsAnnotation(gramp)) {
hasAnnotation = true;
}
nameNodes.add(parent);
} else if (parent.isAssign()) {
if (hasNoSideEffectsAnnotation(parent)) {
hasAnnotation = true;
}
nameNodes.add(parent.getFirstChild());
}
if (hasAnnotation) {
noSideEffectFunctionNames.addAll(nameNodes);
}
}
}
}
/**
* Set the no side effects property for CALL and NEW nodes that
* refer to function names that are known to have no side effects.
*/
private class SetNoSideEffectCallProperty extends AbstractPostOrderCallback {
private final SimpleDefinitionFinder defFinder;
SetNoSideEffectCallProperty(SimpleDefinitionFinder defFinder) {
this.defFinder = defFinder;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
if (!node.isCall() && !node.isNew()) {
return;
}
Collection<Definition> definitions =
defFinder.getDefinitionsReferencedAt(node.getFirstChild());
if (definitions == null) {
return;
}
for (Definition def : definitions) {
Node lValue = def.getLValue();
Preconditions.checkNotNull(lValue);
if (!noSideEffectFunctionNames.contains(lValue) &&
definitionTypeContainsFunctionType(def)) {
return;
}
}
node.setSideEffectFlags(Node.NO_SIDE_EFFECTS);
}
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>(useSite);
if (name != null) {
Collection<Definition> defs = nameDefinitionMultimap.get(name);
if (!defs.isEmpty()) {
return defs;
} else {
return null;
}
} else {
return null;
}
}
@Override
public void process(Node externs, Node source) {
NodeTraversal.traverse(
compiler, externs, new DefinitionGatheringCallback(true));
NodeTraversal.traverse(
compiler, source, new DefinitionGatheringCallback(false));
NodeTraversal.traverse(
compiler, source, new UseSiteGatheringCallback());
}
/**
* Returns a collection of use sites that may refer to provided
* definition. Returns an empty collection if the definition is not
* used anywhere.
*
* @param definition Definition of interest.
* @return use site collection.
*/
Collection<UseSite> getUseSites(Definition definition) {
String name = getSimplifiedName(definition.getLValue());
return nameUseSiteMultimap.get(name);
}
/**
* Extract a name from a node. In the case of GETPROP nodes,
* replace the namespace or object expression with "this" for
* simplicity and correctness at the expense of inefficiencies due
* to higher chances of name collisions.
*
* TODO(user) revisit. it would be helpful to at least use fully
* qualified names in the case of namespaces. Might not matter as
* much if this pass runs after "collapsing properties".
*/
private static String getSimplifiedName(Node node) {
if (node.isName()) {
String name = node.getString();
if (name != null && !name.isEmpty()) {
return name;
} else {
return null;
}
} else if (node.isGetProp()) {
return "this." + node.getLastChild().getString();
}
return null;
}
private class DefinitionGatheringCallback extends AbstractPostOrderCallback {
private boolean inExterns;
DefinitionGatheringCallback(boolean inExterns) {
this.inExterns = inExterns;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
// Arguments of external functions should not count as name
// definitions. They are placeholder names for documentation
// purposes only which are not reachable from anywhere.
if (inExterns && node.isName() && parent.isParamList()) {
return;
}
Definition def =
DefinitionsRemover.getDefinition(node, inExterns);
if (def != null) {
String name = getSimplifiedName(def.getLValue());
if (name != null) {
Node rValue = def.getRValue();
if ((rValue != null) &&
!NodeUtil.isImmutableValue(rValue) &&
!rValue.isFunction()) {
// Unhandled complex expression
Definition unknownDef =
new UnknownDefinition(def.getLValue(), inExterns);
def = unknownDef;
}
// TODO(johnlenz) : remove this stub dropping code if it becomes
// illegal to have untyped stubs in the externs definitions.
if (inExterns) {
// We need special handling of untyped externs stubs here:
// the stub should be dropped if the name is provided elsewhere.
List<Definition> stubsToRemove = Lists.newArrayList();
String qualifiedName = node.
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> private Deque<GraphAnnotationState> nodeAnnotationStack;
/**
* Used by {@link #pushEdgeAnnotations()} and {@link #popEdgeAnnotations()}.
*/
private Deque<GraphAnnotationState> edgeAnnotationStack;
/**
* Connects two nodes in the graph with an edge.
*
* @param n1 First node.
* @param edge The edge.
* @param n2 Second node.
*/
public abstract void connect(N n1, E edge, N n2);
/**
* Disconnects two nodes in the graph by removing all edges between them.
*
* @param n1 First node.
* @param n2 Second node.
*/
public abstract void disconnect(N n1, N n2);
/**
* Connects two nodes in the graph with an edge if such edge does not already
* exists between the nodes.
*
* @param n1 First node.
* @param edge The edge.
* @param n2 Second node.
*/
public final void connectIfNotFound(N n1, E edge, N n2) {
if (!isConnected(n1, edge, n2)) {
connect(n1, edge, n2);
}
}
/**
* Gets a node from the graph given a value. New nodes are created if that
* value has not been assigned a graph node. Values equality are compared
* using <code>Object.equals</code>.
*
* @param value The node's value.
* @return The corresponding node in the graph.
*/
public abstract GraphNode<N, E> createNode(N value);
/** Gets an immutable list of all nodes. */
@Override
public abstract Collection<GraphNode<N, E>> getNodes();
/** Gets an immutable list of all edges. */
public abstract List<GraphEdge<N, E>> getEdges();
/**
* Gets the degree of a node.
*
* @param value The node's value.
* @return The degree of the node.
*/
public abstract int getNodeDegree(N value);
@Override
public int getWeight(N value) {
return getNodeDegree(value);
}
/**
* Gets the neighboring nodes.
*
* @param value The node's value.
* @return A list of neighboring nodes.
*/
public abstract List<GraphNode<N, E>> getNeighborNodes(N value);
public abstract Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value);
/**
* Retrieves an edge from the graph.
*
* @param n1 Node one.
* @param n2 Node two.
* @return The list of edges between those two values in the graph.
*/
public abstract List<GraphEdge<N, E>> getEdges(N n1, N n2);
/**
* Retrieves any edge from the graph.
*
* @param n1 Node one.
* @param n2 Node two.
* @return The first edges between those two values in the graph. null if
* there are none.
*/
public abstract GraphEdge<N, E> getFirstEdge(N n1, N n2);
/**
* Checks whether the node exists in the graph ({@link #createNode(Object)}
* has been called with that value).
*
* @param n Node.
* @return <code>true</code> if it exist.
*/
public final boolean hasNode(N n) {
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> return getNode(n) != null;
}
/**
* Checks whether two nodes in the graph are connected.
*
* @param n1 Node 1.
* @param n2 Node 2.
* @return <code>true</code> if the two nodes are connected.
*/
public abstract boolean isConnected(N n1, N n2);
/**
* Checks whether two nodes in the graph are connected by the given
* edge type.
*
* @param n1 Node 1.
* @param e The edge type.
* @param n2 Node 2.
*/
public abstract boolean isConnected(N n1, E e, N n2);
/**
* Gets the node of the specified type, or throws an
* IllegalArgumentException.
*/
@SuppressWarnings("unchecked")
<T extends GraphNode<N, E>> T getNodeOrFail(N val) {
T node = (T) getNode(val);
if (node == null) {
throw new IllegalArgumentException(val + " does not exist in graph");
}
return node;
}
@Override
public final void clearNodeAnnotations() {
for (GraphNode<N, E> n : getNodes()) {
n.setAnnotation(null);
}
}
/** Makes each edge's annotation null. */
public final void clearEdgeAnnotations() {
for (GraphEdge<N, E> e : getEdges()) {
e.setAnnotation(null);
}
}
/**
* Pushes nodes' annotation values. Restored with
* {@link #popNodeAnnotations()}. Nodes' annotation values are cleared.
*/
public final void pushNodeAnnotations() {
if (nodeAnnotationStack == null) {
nodeAnnotationStack = Lists.newLinkedList();
}
pushAnnotations(nodeAnnotationStack, getNodes());
}
/**
* Restores nodes' annotation values to state before last
* {@link #pushNodeAnnotations()}.
*/
public final void popNodeAnnotations() {
Preconditions.checkNotNull(nodeAnnotationStack,
"Popping node annotations without pushing.");
popAnnotations(nodeAnnotationStack);
}
/**
* Pushes edges' annotation values. Restored with
* {@link #popEdgeAnnotations()}. Edges' annotation values are cleared.
*/
public final void pushEdgeAnnotations() {
if (edgeAnnotationStack == null) {
edgeAnnotationStack = Lists.newLinkedList();
}
pushAnnotations(edgeAnnotationStack, getEdges());
}
/**
* Restores edges' annotation values to state before last
* {@link #pushEdgeAnnotations()}.
*/
public final void popEdgeAnnotations() {
Preconditions.checkNotNull(edgeAnnotationStack,
"Popping edge annotations without pushing.");
popAnnotations(edgeAnnotationStack);
}
/**
* A generic edge.
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public interface GraphEdge<N, E> extends Annotatable {
/**
* Retrieves the edge's value.
*
* @return The value.
*/
E getValue();
GraphNode<N, E> getNodeA();
GraphNode<N, E> getNodeB();
}
/**
* A simple implementation of SubGraph that calculates adjacency by iterating
* over a node's neighbors.
*/
class SimpleSubGraph<N, E> implements SubGraph<N, E> {
private Graph<N, E> graph;
private List<GraphNode<N, E
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>>> nodes = Lists.newArrayList();
SimpleSubGraph(Graph<N, E> graph) {
this.graph = graph;
}
@Override
public boolean isIndependentOf(N value) {
GraphNode<N, E> node = graph.getNode(value);
for (GraphNode<N, E> n : nodes) {
if (graph.getNeighborNodes(n.getValue()).contains(node)) {
return false;
}
}
return true;
}
@Override
public void addNode(N value) {
nodes.add(graph.getNodeOrFail(value));
}
}
/**
* Pushes a new list on stack and stores nodes annotations in the new list.
* Clears objects' annotations as well.
*/
private static void pushAnnotations(
Deque<GraphAnnotationState> stack,
Collection<? extends Annotatable> haveAnnotations) {
stack.push(new GraphAnnotationState(haveAnnotations.size()));
for (Annotatable h : haveAnnotations) {
stack.peek().add(new AnnotationState(h, h.getAnnotation()));
h.setAnnotation(null);
}
}
/**
* Restores the node annotations on the top of stack and pops stack.
*/
private static void popAnnotations(Deque<GraphAnnotationState> stack) {
for (AnnotationState as : stack.pop()) {
as.first.setAnnotation(as.second);
}
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Predicate;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.GraphReachability;
import com.google.javascript.jscomp.graph.GraphReachability.EdgeTuple;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.TernaryValue;
/**
* Use {@link ControlFlowGraph} and {@link GraphReachability} to inform user
* about unreachable code.
*
*/
class CheckUnreachableCode implements ScopedCallback {
static final DiagnosticType UNREACHABLE_CODE = DiagnosticType.error(
"JSC_UNREACHABLE_CODE", "unreachable code");
private final AbstractCompiler compiler;
private final CheckLevel level;
CheckUnreachableCode(AbstractCompiler compiler, CheckLevel level) {
this.compiler = compiler;
this.level = level;
}
@Override
public void enterScope(NodeTraversal t) {
initScope(t.getControlFlowGraph());
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
GraphNode<Node, Branch> gNode = t.getControlFlowGraph().getNode(n);
if (gNode != null && gNode.getAnnotation() != GraphReachability.REACHABLE) {
// Only report error when there are some line number informations.
// There are synthetic nodes with no line number informations, nodes
// introduce by other passes (although not likely since this pass should
// be executed early) or some rhino bug.
if (n.getLineno() != -1 &&
// Allow spurious semi-colons and spurious breaks.
!n.isEmpty() && !n.isBreak()) {
compiler.report(t.makeError(n, level, UNREACHABLE_CODE));
// From now on, we are going to assume the user fixed the error and not
// give more warning related to code section reachable from this node.
new GraphReachability<Node, ControlFlowGraph.Branch>(
t.getControlFlowGraph()).recompute(n);
// Saves time by not traversing children.
return false;
}
}
return true;
}
private void initScope(ControlFlowGraph<Node> controlFlowGraph) {
new GraphReachability<Node, ControlFlowGraph.Branch>(
controlFlowGraph, new ReachablePredicate()).compute(
controlFlowGraph.getEntry().getValue());
}
@Override
public void exitScope(NodeTraversal t) {
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> @Override
public void visit(NodeTraversal t, Node n, Node parent) {
}
private final class ReachablePredicate implements
Predicate<EdgeTuple<Node, ControlFlowGraph.Branch>> {
@Override
public boolean apply(EdgeTuple<Node, Branch> input) {
Branch branch = input.edge;
if (!branch.isConditional()) {
return true;
}
Node predecessor = input.sourceNode;
Node condition = NodeUtil.getConditionExpression(predecessor);
// TODO(user): Handle more complicated expression like true == true,
// etc....
if (condition != null) {
TernaryValue val = NodeUtil.getImpureBooleanValue(condition);
if (val != TernaryValue.UNKNOWN) {
return val.toBoolean(true) == (branch == Branch.ON_TRUE);
}
}
return true;
}
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> It captures a product
* lattice for each local (non-escaped) variable. The sub-lattice is
* a n + 2 element lattice with all the {@link Definition} in the program,
* TOP and BOTTOM.
*
* <p>Since this is a Must-Define analysis, BOTTOM represents the case where
* there might be more than one reaching definition for the variable.
*
*
* (TOP)
* / | | \
* N1 N2 N3 ....Nn
* \ | | /
* (BOTTOM)
*
*/
static final class MustDef implements LatticeElement {
// TODO(user): Use bit vector instead of maps might get better
// performance. Change it after this is tested to be fully functional.
// When a Var "A" = "TOP", "A" does not exist in reachingDef's keySet.
// When a Var "A" = Node N, "A" maps to that node.
// When a Var "A" = "BOTTOM", "A" maps to null.
final Map<Var, Definition> reachingDef;
public MustDef() {
reachingDef = Maps.newHashMap();
}
public MustDef(Iterator<Var> vars) {
this();
while(vars.hasNext()) {
Var var = vars.next();
// Every variable in the scope is defined once in the beginning of the
// function: all the declared variables are undefined, all functions
// have been assigned and all arguments has its value from the caller.
reachingDef.put(var, new Definition(var.scope.getRootNode()));
}
}
/**
* Copy constructor.
*
* @param other The constructed object is a replicated copy of this element.
*/
public MustDef(MustDef other) {
reachingDef = Maps.newHashMap(other.reachingDef);
}
@Override
public boolean equals(Object other) {
return (other instanceof MustDef) &&
((MustDef) other).reachingDef.equals(this.reachingDef);
}
}
private static class MustDefJoin extends JoinOp.BinaryJoinOp<MustDef> {
@Override
public MustDef apply(MustDef a, MustDef b) {
MustDef result = new MustDef();
Map<Var, Definition> resultMap = result.reachingDef;
// Take the join of all variables that are not TOP in this.
for (Map.Entry<Var, Definition> varEntry : a.reachingDef.entrySet()) {
Var var = varEntry.getKey();
Definition aDef = varEntry.getValue();
if (aDef == null) {
// "a" is BOTTOM implies that the variable has more than one possible
// definition. We set the join of this to be BOTTOM regardless of what
// "b" might be.
resultMap.put(var, null);
continue;
}
Node aNode = aDef.node;
if (b.reachingDef.containsKey(var)) {
Definition bDef = b.reachingDef.get(var);
if (aDef.equals(bDef)) {
resultMap.put(var, aDef);
} else {
resultMap.put(var, null);
}
} else {
resultMap.put(var, aDef);
}
}
// Take the join of all variables that are not TOP in other but it is TOP
// in this.
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> for (Map.Entry<Var, Definition> entry : b.reachingDef.entrySet()) {
Var var = entry.getKey();
if (!a.reachingDef.containsKey(var)) {
resultMap.put(var, entry.getValue());
}
}
return result;
}
}
@Override
boolean isForward() {
return true;
}
@Override
MustDef createEntryLattice() {
return new MustDef(jsScope.getVars());
}
@Override
MustDef createInitialEstimateLattice() {
return new MustDef();
}
@Override
MustDef flowThrough(Node n, MustDef input) {
// TODO(user): We are doing a straight copy from input to output. There
// might be some opportunities to cut down overhead.
MustDef output = new MustDef(input);
// TODO(user): This must know about ON_EX edges but it should handle
// it better than what we did in liveness. Because we are in a forward mode,
// we can used the branched forward analysis.
computeMustDef(n, n, output, false);
return output;
}
/**
* @param n The node in question.
* @param cfgNode The node to add
* @param conditional true if the definition is not always executed.
*/
private void computeMustDef(
Node n, Node cfgNode, MustDef output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.WHILE:
case Token.DO:
case Token.IF:
computeMustDef(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
return;
case Token.FOR:
if (!NodeUtil.isForIn(n)) {
computeMustDef(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
} else {
// for(x in y) {...}
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
if (lhs.isVar()) {
lhs = lhs.getLastChild(); // for(var x in y) {...}
}
if (lhs.isName()) {
addToDefIfLocal(lhs.getString(), cfgNode, rhs, output);
}
}
return;
case Token.AND:
case Token.OR:
computeMustDef(n.getFirstChild(), cfgNode, output, conditional);
computeMustDef(n.getLastChild(), cfgNode, output, true);
return;
case Token.HOOK:
computeMustDef(n.getFirstChild(), cfgNode, output, conditional);
computeMustDef(n.getFirstChild().getNext(), cfgNode, output, true);
computeMustDef(n.getLastChild(), cfgNode, output, true);
return;
case Token.VAR:
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.hasChildren()) {
computeMustDef(c.getFirstChild(), cfgNode, output, conditional);
addToDefIfLocal(c.getString(), conditional ? null : cfgNode,
c.getFirstChild(), output);
}
}
return;
default:
if (NodeUtil.isAssignmentOp(n)) {
if (n.getFirstChild().isName()) {
Node name = n.getFirstChild();
computeMustDef(name.getNext(), cfgNode, output, conditional);
addToDefIfLocal(
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>name.getString(), conditional ? null : cfgNode,
n.getLastChild(), output);
return;
} else if (NodeUtil.isGet(n.getFirstChild())) {
// Treat all assignments to arguments as redefining the
// parameters itself.
Node obj = n.getFirstChild().getFirstChild();
if (obj.isName() && "arguments".equals(obj.getString())) {
// TODO(user): More accuracy can be introduced
// i.e. We know exactly what arguments[x] is if x is a constant
// number.
escapeParameters(output);
}
}
}
if (n.isName() && "arguments".equals(n.getString())) {
escapeParameters(output);
}
// DEC and INC actually defines the variable.
if (n.isDec() || n.isInc()) {
Node target = n.getFirstChild();
if (target.isName()) {
addToDefIfLocal(target.getString(),
conditional ? null : cfgNode, null, output);
return;
}
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
computeMustDef(c, cfgNode, output, conditional);
}
}
}
/**
* Set the variable lattice for the given name to the node value in the def
* lattice. Do nothing if the variable name is one of the escaped variable.
*
* @param node The CFG node where the definition should be record to.
* {@code null} if this is a conditional define.
*/
private void addToDefIfLocal( String name, @Nullable Node node,
@Nullable Node rValue, MustDef def) {
Var var = jsScope.getVar(name);
// var might be null because the variable might be defined in the extern
// that we might not traverse.
if (var == null || var.scope != jsScope) {
return;
}
for (Var other : def.reachingDef.keySet()) {
Definition otherDef = def.reachingDef.get(other);
if (otherDef == null) {
continue;
}
if (otherDef.depends.contains(var)) {
def.reachingDef.put(other, null);
}
}
if (!escaped.contains(var)) {
if (node == null) {
def.reachingDef.put(var, null);
} else {
Definition definition = new Definition(node);
if (rValue != null) {
computeDependence(definition, rValue);
}
def.reachingDef.put(var, definition);
}
}
}
private void escapeParameters(MustDef output) {
for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) {
Var v = i.next();
if (isParameter(v)) {
// Assume we no longer know where the parameter comes from
// anymore.
output.reachingDef.put(v, null);
}
}
// Also, assume we no longer know anything that depends on a parameter.
for (Entry<Var, Definition> pair: output.reachingDef.entrySet()) {
Definition value = pair.getValue();
if (value == null) {
continue;
}
for (Var dep : value.depends) {
if (isParameter(dep)) {
output.reachingDef.put(pair.getKey(), null);
}
}
}
}
private boolean isParameter(Var
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> v) {
return v.getParentNode().isParamList();
}
/**
* Computes all the local variables that rValue reads from and store that
* in the def's depends set.
*/
private void computeDependence(final Definition def, Node rValue) {
NodeTraversal.traverse(compiler, rValue,
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
Var dep = jsScope.getVar(n.getString());
if (dep == null) {
def.unknownDependencies = true;
} else {
def.depends.add(dep);
}
}
}
});
}
/**
* Gets the must reaching definition of a given node.
*
* @param name name of the variable. It can only be names of local variable
* that are not function parameters, escaped variables or variables
* declared in catch.
* @param useNode the location of the use where the definition reaches.
*/
Definition getDef(String name, Node useNode) {
Preconditions.checkArgument(getCfg().hasNode(useNode));
GraphNode<Node, Branch> n = getCfg().getNode(useNode);
FlowState<MustDef> state = n.getAnnotation();
return state.getIn().reachingDef.get(jsScope.getVar(name));
}
Node getDefNode(String name, Node useNode) {
Definition def = getDef(name, useNode);
return def == null ? null : def.node;
}
boolean dependsOnOuterScopeVars(Definition def) {
if (def.unknownDependencies) {
return true;
}
for (Var s : def.depends) {
if (s.scope != jsScope) {
return true;
}
}
return false;
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
return;
}
finder.visitTree(getAstRoot(compiler));
// TODO(nicksantos|user): This caching behavior is a bit
// odd, and only works if you assume the exact call flow that
// clients are currently using. In that flow, they call
// getProvides(), then remove the goog.provide calls from the
// AST, and then call getProvides() again.
//
// This won't work for any other call flow, or any sort of incremental
// compilation scheme. The API needs to be fixed so callers aren't
// doing weird things like this, and then we should get rid of the
// multiple-scan strategy.
provides.addAll(finder.provides);
requires.addAll(finder.requires);
} else {
// Otherwise, look at the source code.
if (!generatedDependencyInfoFromSource) {
// Note: it's OK to use getName() instead of
// getPathRelativeToClosureBase() here because we're not using
// this to generate deps files. (We're only using it for
// symbol dependencies.)
DependencyInfo info =
(new JsFileParser(compiler.getErrorManager()))
.setIncludeGoogBase(true)
.parseFile(getName(), getName(), getCode());
provides.addAll(info.getProvides());
requires.addAll(info.getRequires());
generatedDependencyInfoFromSource = true;
}
}
}
private static class DepsFinder {
private final List<String> provides = Lists.newArrayList();
private final List<String> requires = Lists.newArrayList();
private final CodingConvention codingConvention =
new ClosureCodingConvention();
void visitTree(Node n) {
visitSubtree(n, null);
}
void visitSubtree(Node n, Node parent) {
if (n.isCall()) {
String require =
codingConvention.extractClassNameIfRequire(n, parent);
if (require != null) {
requires.add(require);
}
String provide =
codingConvention.extractClassNameIfProvide(n, parent);
if (provide != null) {
provides.add(provide);
}
return;
} else if (parent != null &&
!parent.isExprResult() &&
!parent.isScript()) {
return;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
visitSubtree(child, n);
}
}
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
return getSourceFile().getLine(lineNumber);
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public Region getRegion(int lineNumber) {
return getSourceFile().getRegion(lineNumber);
}
public String
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Filters warnings based on in-code {@code @suppress} annotations.
* @author nicksantos@google.com (Nick Santos)
*/
class SuppressDocWarningsGuard extends WarningsGuard {
private static final long serialVersionUID = 1L;
/** Warnings guards for each suppressible warnings group, indexed by name. */
private final Map<String, DiagnosticGroupWarningsGuard> suppressors =
Maps.newHashMap();
/**
* The suppressible groups, indexed by name.
*/
SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) {
for (Map.Entry<String, DiagnosticGroup> entry :
suppressibleGroups.entrySet()) {
suppressors.put(
entry.getKey(),
new DiagnosticGroupWarningsGuard(
entry.getValue(),
CheckLevel.OFF));
}
}
@Override
public CheckLevel level(JSError error) {
Node node = error.node;
if (node != null) {
for (Node current = node;
current != null;
current = current.getParent()) {
int type = current.getType();
JSDocInfo info = null;
// We only care about function annotations at the FUNCTION and SCRIPT
// level. Otherwise, the @suppress annotation has an implicit
// dependency on the exact structure of our AST, and that seems like
// a bad idea.
if (type == Token.FUNCTION) {
info = NodeUtil.getFunctionJSDocInfo(current);
} else if (type == Token.SCRIPT) {
info = current.getJSDocInfo();
} else if (type == Token.ASSIGN) {
Node rhs = current.getLastChild();
if (rhs.isFunction()) {
info = NodeUtil.getFunctionJSDocInfo(rhs);
}
}
if (info != null) {
for (String suppressor : info.getSuppressions()) {
WarningsGuard guard = suppressors.get(suppressor);
// Some @suppress tags are for other tools, and
// may not have a warnings guard.
if (guard != null) {
CheckLevel newLevel = guard.level(error);
if (newLevel != null) {
return newLevel;
}
}
}
}
}
}
return null;
}
@Override
public int getPriority() {
// Happens after path-based filtering, but before other times
// of filtering.
return WarningsGuard.Priority.SUPPRESS_DOC.value;
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> be scattered across reports).
*
* Java objects do not support destructors (as in C++) so Tracer is not robust
* when exceptions are thrown. Each Tracer object should be wrapped in a
* try/finally block so that if an exception is thrown, the Tracer.stop()
* method is guaranteed to be called.
*
* <p>A thread must call {@link Tracer#initCurrentThreadTrace()} to enable the
* Tracer logging, otherwise Tracer does nothing. The requirement to call
* {@code initCurrentThreadTrace} avoids the situation where Tracer is called
* without the explicit knowledge of the application authors because they
* happen to use a class in another package that uses Tracer. If {@link
* Tracer#logCurrentThreadTrace} is called without calling {@link
* Tracer#initCurrentThreadTrace()}, then a Third Eye WARNING message is logged,
* which should help track down the problem.
*
*/
final class Tracer {
// package-private for access from unit tests
static final Logger logger =
Logger.getLogger(Tracer.class.getName());
/**
* Whether pretty printing is enabled. This is intended to be set once
* at application startup.
*/
private static volatile boolean defaultPrettyPrint;
/* This list is guaranteed to only increase in length. It contains
* a list of additional statistics that the user wants to keep track
* of.
*/
private static List<TracingStatistic> extraTracingStatistics =
new CopyOnWriteArrayList<TracingStatistic>();
/** Values returned by extraTracingStatistics */
private long[] extraTracingValues;
/** The type for grouping traces, may be null */
private final @Nullable String type;
/** A comment string for the report */
private final String comment;
/** Start time of the trace */
private final long startTimeMs;
/** Stop time of the trace, non-final */
private long stopTimeMs;
/**
* Record our starter thread in order to trap Traces that are started in one
* thread and stopped in another
*/
final Thread startThread;
/**
* We limit the number of events in a Trace in order to catch memory
* leaks (a thread that keeps logging events and never clears them).
* This number is arbitrary and can be increased if necessary (though
* if there are more than 1000 events then the Tracer is probably being
* misused).
*/
static final int MAX_TRACE_SIZE = 1000;
/**
* For unit testing. Can't use {@link com.google.common.time.Clock} because
* this code is in base and has minimal dependencies.
*/
static interface InternalClock {
long currentTimeMillis();
}
/**
* Default clock that calls through to the system clock. Can be overridden
* in unit tests.
*/
static InternalClock clock = new InternalClock() {
@Override
public long currentTimeMillis() {
return System.currentTimeMillis();
}
};
/**
* Create and start a tracer.
* Both type and comment may be null. See class comment for usage.
*
* @param type The type for totaling
* @param comment Comment about this tracer
*/
Tracer(@Nullable String type, @Nullable String comment) {
this.type = type;
this.comment = comment == null ? "" : comment;
startTimeMs = clock.currentTimeMillis();
startThread = Thread.currentThread();
if (!extraTracingStatistics.isEmpty()) {
int size = extraTracingStatistics.size();
extraTracing
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
private final AbstractCompiler compiler;
private final Set<Var> inlinedNewDependencies = Sets.newHashSet();
// These two pieces of data is persistent in the whole execution of enter
// scope.
private ControlFlowGraph<Node> cfg;
private List<Candidate> candidates;
private MustBeReachingVariableDef reachingDef;
private MaybeReachingVariableUse reachingUses;
private static final Predicate<Node> SIDE_EFFECT_PREDICATE =
new Predicate<Node>() {
@Override
public boolean apply(Node n) {
// When the node is null it means, we reached the implicit return
// where the function returns (possibly without an return statement)
if (n == null) {
return false;
}
// TODO(user): We only care about calls to functions that
// passes one of the dependent variable to a non-side-effect free
// function.
if (n.isCall() && NodeUtil.functionCallHasSideEffects(n)) {
return true;
}
if (n.isNew() && NodeUtil.constructorCallHasSideEffects(n)) {
return true;
}
if (n.isDelProp()) {
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && apply(c)) {
return true;
}
}
return false;
}
};
public FlowSensitiveInlineVariables(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return; // Don't even brother. All global variables are likely escaped.
}
if (LiveVariablesAnalysis.MAX_VARIABLES_TO_ANALYZE <
t.getScope().getVarCount()) {
return;
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
if (c.canInline()) {
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
}
}
@Override
public void exitScope(NodeTraversal
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> t) {}
@Override
public void process(Node externs, Node root) {
(new NodeTraversal(compiler, this)).traverseRoots(externs, root);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// TODO(user): While the helpers do a subtree traversal on the AST, the
// compiler pass itself only traverse the AST to look for function
// declarations to perform dataflow analysis on. We could combine
// the traversal in DataFlowAnalysis's computeEscaped later to save some
// time.
}
/**
* Gathers a list of possible candidates for inlining based only on
* information from {@link MustBeReachingVariableDef}. The list will be stored
* in {@code candidates} and the validity of each inlining Candidate should
* be later verified with {@link Candidate#canInline()} when
* {@link MaybeReachingVariableUse} has been performed.
*/
private class GatherCandiates extends AbstractShallowCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
DiGraphNode<Node, Branch> graphNode = cfg.getDirectedGraphNode(n);
if (graphNode == null) {
// Not a CFG node.
return;
}
FlowState<MustDef> state = graphNode.getAnnotation();
final MustDef defs = state.getIn();
final Node cfgNode = n;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
// n.getParent() isn't null. This just the case where n is the root
// node that gatherCb started at.
if (parent == null) {
return;
}
// Make sure that the name node is purely a read.
if ((NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)
|| parent.isVar() || parent.isInc() || parent.isDec() ||
parent.isParamList() || parent.isCatch()) {
return;
}
String name = n.getString();
if (compiler.getCodingConvention().isExported(name)) {
return;
}
Definition def = reachingDef.getDef(name, cfgNode);
// TODO(nicksantos): We need to add some notion of @const outer
// scope vars. We can inline those just fine.
if (def != null &&
!reachingDef.dependsOnOuterScopeVars(def)) {
candidates.add(new Candidate(name, def, n, cfgNode));
}
}
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Models the connection between a definition and a use of that definition.
*/
private class Candidate {
// Name of the variable.
private final String varName;
// Nodes related to the definition.
private Node def;
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.NEW:
return true;
}
return false;
}
},
new Predicate<Node>() {
@Override
public boolean apply(Node input) {
// Recurse if the node is not a function.
return !input.isFunction();
}
})) {
return false;
}
// We can skip the side effect check along the paths of two nodes if
// they are just next to each other.
if (NodeUtil.isStatementBlock(getDefCfgNode().getParent()) &&
getDefCfgNode().getNext() != useCfgNode) {
// Similar side effect check as above but this time the side effect is
// else where along the path.
// x = readProp(b); while(modifyProp(b)) {}; print(x);
CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>
pathCheck = new CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>(
cfg,
cfg.getDirectedGraphNode(getDefCfgNode()),
cfg.getDirectedGraphNode(useCfgNode),
SIDE_EFFECT_PREDICATE,
Predicates.
<DiGraphEdge<Node, ControlFlowGraph.Branch>>alwaysTrue(),
false);
if (pathCheck.somePathsSatisfyPredicate()) {
return false;
}
}
return true;
}
/**
* Actual transformation.
*/
private void inlineVariable() {
Node defParent = def.getParent();
Node useParent = use.getParent();
if (def.isAssign()) {
Node rhs = def.getLastChild();
rhs.detachFromParent();
// Oh yes! I have grandparent to remove this.
Preconditions.checkState(defParent.isExprResult());
while (defParent.getParent().isLabel()) {
defParent = defParent.getParent();
}
defParent.detachFromParent();
useParent.replaceChild(use, rhs);
} else if (defParent.isVar()) {
Node rhs = def.getLastChild();
def.removeChild(rhs);
useParent.replaceChild(use, rhs);
} else {
Preconditions.checkState(false, "No other definitions can be inlined.");
}
compiler.reportCodeChange();
}
/**
* Set the def node
*
* @param n A node that has a corresponding CFG node in the CFG.
*/
private void getDefinition(Node n, Node parent) {
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
if (n.getString().equals(varName) && n.hasChildren()) {
def = n;
}
return;
case Token.ASSIGN:
Node lhs = n.getFirstChild();
if (lhs.isName() && lhs.getString().equals(varName)) {
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(Node n, Node parant) {
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.graph;
import java.util.List;
/**
* A graph that can be dumped to a Graphviz DOT file.
* <p>
* An object which can be visualized as a graph should implement this interface.
* The <code>DotFormatter.toDot</code> function can be used to get a
* visualization of the object for debugging purpose.
*
*/
public interface GraphvizGraph {
/**
* Name of the graph.
*
* @return Name of the graph.
*/
String getName();
/**
* Graph type.
*
* @return True if the graph is a directed graph.
*/
boolean isDirected();
/**
* Retrieve a list of nodes in the graph.
*
* @return A list of nodes in the graph.
*/
List<GraphvizNode> getGraphvizNodes();
/**
* Retrieve a list of edges in the graph.
*
* @return A list of edges in the graph.
*/
List<GraphvizEdge> getGraphvizEdges();
/**
* A Graphviz node.
*/
interface GraphvizNode {
/**
* Retrieves the unique ID.
*
* @return A the unique ID of the node.
*/
String getId();
/**
* Retrieves color of the node.
*
* @return The color of the node.
*/
String getColor();
/**
* Retrieves the label of the node.
*
* @return Label of the node.
*/
String getLabel();
}
/**
* A Graphviz edge.
*/
interface GraphvizEdge {
/**
* Get the first node in the edge. In a directed node, this will be the
* source node.
*
* @return First node in the edge.
*/
String getNode1Id();
/**
* Get the second node in the edge. In a directed node, this will be the
* destination node.
*
* @return First node in the edge.
*/
String getNode2Id();
/**
* Retrieves color of the edge.
*
* @return The color of the edge.
*/
String getColor();
/**
* Retrieves the label of the edge.
*
* @return Label of the edge.
*/
String getLabel();
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import static com.google.javascript.rhino.jstype.JSTypeNative.ALL_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.CHECKED_UNKNOWN_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.NO_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.UNKNOWN_TYPE;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.javascript.rhino.jstype.UnionType;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* A builder for union types.
*
* @author nicksantos@google.com (Nick Santos)
*/
class UnionTypeBuilder implements Serializable {
private static final long serialVersionUID = 1L;
// If the best we can do is say "this object is one of twenty things",
// then we should just give up and admit that we have no clue.
private static final int DEFAULT_MAX_UNION_SIZE = 20;
private final JSTypeRegistry registry;
private final List<JSType> alternates = Lists.newArrayList();
private boolean isAllType = false;
private boolean isNativeUnknownType = false;
private boolean areAllUnknownsChecked = true;
private final int maxUnionSize;
// Every Union
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>GetterFunction() {
return registerFunction != null;
}
String getName() {
return name;
}
String getExpectedTypeName() {
return expectedTypeName;
}
Node createDefaultValueNode() {
switch (this) {
case REGISTER_BOOLEAN:
return IR.falseNode();
case REGISTER_NUMBER:
return IR.number(0);
case REGISTER_STRING:
return IR.string("");
}
throw new IllegalStateException();
}
}
// A map of function name -> TweakFunction.
private static final Map<String, TweakFunction> TWEAK_FUNCTIONS_MAP;
static {
TWEAK_FUNCTIONS_MAP = Maps.newHashMap();
for (TweakFunction func : TweakFunction.values()) {
TWEAK_FUNCTIONS_MAP.put(func.getName(), func);
}
}
ProcessTweaks(AbstractCompiler compiler, boolean stripTweaks,
Map<String, Node> compilerDefaultValueOverrides) {
this.compiler = compiler;
this.stripTweaks = stripTweaks;
// Having the map sorted is required for the unit tests to be deterministic.
this.compilerDefaultValueOverrides = Maps.newTreeMap();
this.compilerDefaultValueOverrides.putAll(compilerDefaultValueOverrides);
}
@Override
public void process(Node externs, Node root) {
CollectTweaksResult result = collectTweaks(root);
applyCompilerDefaultValueOverrides(result.tweakInfos);
boolean changed = false;
if (stripTweaks) {
changed = stripAllCalls(result.tweakInfos);
} else if (!compilerDefaultValueOverrides.isEmpty()) {
changed = replaceGetCompilerOverridesCalls(result.getOverridesCalls);
}
if (changed) {
compiler.reportCodeChange();
}
}
/**
* Passes the compiler default value overrides to the JS by replacing calls
* to goog.tweak.getCompilerOverrids_ with a map of tweak ID->default value;
*/
private boolean replaceGetCompilerOverridesCalls(
List<TweakFunctionCall> calls) {
for (TweakFunctionCall call : calls) {
Node callNode = call.callNode;
Node objNode = createCompilerDefaultValueOverridesVarNode(callNode);
callNode.getParent().replaceChild(callNode, objNode);
}
return !calls.isEmpty();
}
/**
* Removes all CALL nodes in the given TweakInfos, replacing calls to getter
* functions with the tweak's default value.
*/
private boolean stripAllCalls(Map<String, TweakInfo> tweakInfos) {
for (TweakInfo tweakInfo : tweakInfos.values()) {
boolean isRegistered = tweakInfo.isRegistered();
for (TweakFunctionCall functionCall : tweakInfo.functionCalls) {
Node callNode = functionCall.callNode;
Node parent = callNode.getParent();
if (functionCall.tweakFunc.isGetterFunction()) {
Node newValue;
if (isRegistered) {
newValue = tweakInfo.getDefaultValueNode().cloneNode();
} else {
// When we find a getter of an unregistered tweak, there has
// already been a warning about it, so now just use a default
// value when stripping.
TweakFunction registerFunction =
functionCall.tweakFunc.registerFunction;
newValue = registerFunction.createDefaultValueNode();
}
parent.replaceChild(callNode, newValue);
} else {
Node voidZeroNode = IR.voidNode(IR.number(
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>0).srcref(callNode))
.srcref(callNode);
parent.replaceChild(callNode, voidZeroNode);
}
}
}
return !tweakInfos.isEmpty();
}
/**
* Creates a JS object that holds a map of tweakId -> default value override.
*/
private Node createCompilerDefaultValueOverridesVarNode(
Node sourceInformationNode) {
Node objNode = IR.objectlit().srcref(sourceInformationNode);
for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
Node objKeyNode = IR.stringKey(entry.getKey())
.copyInformationFrom(sourceInformationNode);
Node objValueNode = entry.getValue().cloneNode()
.copyInformationFrom(sourceInformationNode);
objKeyNode.addChildToBack(objValueNode);
objNode.addChildToBack(objKeyNode);
}
return objNode;
}
/** Sets the default values of tweaks based on compiler options. */
private void applyCompilerDefaultValueOverrides(
Map<String, TweakInfo> tweakInfos) {
for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
String tweakId = entry.getKey();
TweakInfo tweakInfo = tweakInfos.get(tweakId);
if (tweakInfo == null) {
compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId));
} else {
TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc;
Node value = entry.getValue();
if (!registerFunc.isValidNodeType(value.getType())) {
compiler.report(JSError.make(INVALID_TWEAK_DEFAULT_VALUE_WARNING,
tweakId, registerFunc.getName(),
registerFunc.getExpectedTypeName()));
} else {
tweakInfo.defaultValueNode = value;
}
}
}
}
/**
* Finds all calls to goog.tweak functions and emits warnings/errors if any
* of the calls have issues.
* @return A map of {@link TweakInfo} structures, keyed by tweak ID.
*/
private CollectTweaksResult collectTweaks(Node root) {
CollectTweaks pass = new CollectTweaks();
NodeTraversal.traverse(compiler, root, pass);
Map<String, TweakInfo> tweakInfos = pass.allTweaks;
for (TweakInfo tweakInfo: tweakInfos.values()) {
tweakInfo.emitAllWarnings();
}
return new CollectTweaksResult(tweakInfos, pass.getOverridesCalls);
}
private final static class CollectTweaksResult {
final Map<String, TweakInfo> tweakInfos;
final List<TweakFunctionCall> getOverridesCalls;
CollectTweaksResult(Map<String, TweakInfo> tweakInfos,
List<TweakFunctionCall> getOverridesCalls) {
this.tweakInfos = tweakInfos;
this.getOverridesCalls = getOverridesCalls;
}
}
/**
* Processes all calls to goog.tweak functions.
*/
private final class CollectTweaks extends AbstractPostOrderCallback {
final Map<String, TweakInfo> allTweaks = Maps.newHashMap();
final List<TweakFunctionCall> getOverridesCalls = Lists.newArrayList();
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isCall()) {
return;
}
String callName = n.getFirstChild().getQualifiedName();
TweakFunction
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
this.sourceName = sourceName;
this.callNode = callNode;
this.tweakFunc = tweakFunc;
this.valueNode = valueNode;
}
Node getIdNode() {
return callNode.getFirstChild().getNext();
}
}
/**
* Stores information about a single tweak.
*/
private final class TweakInfo {
final String tweakId;
final List<TweakFunctionCall> functionCalls;
TweakFunctionCall registerCall;
Node defaultValueNode;
TweakInfo(String tweakId) {
this.tweakId = tweakId;
functionCalls = Lists.newArrayList();
}
/**
* If this tweak is registered, then looks for type warnings in default
* value parameters and getter functions. If it is not registered, emits an
* error for each function call.
*/
void emitAllWarnings() {
if (isRegistered()) {
emitAllTypeWarnings();
} else {
emitUnknownTweakErrors();
}
}
/**
* Emits a warning for each default value parameter that has the wrong type
* and for each getter function that was used for the wrong type of tweak.
*/
void emitAllTypeWarnings() {
for (TweakFunctionCall call : functionCalls) {
Node valueNode = call.valueNode;
TweakFunction tweakFunc = call.tweakFunc;
TweakFunction registerFunc = registerCall.tweakFunc;
if (valueNode != null) {
// For register* and overrideDefaultValue calls, ensure the default
// value is a literal of the correct type.
if (!registerFunc.isValidNodeType(valueNode.getType())) {
compiler.report(JSError.make(call.sourceName,
valueNode, INVALID_TWEAK_DEFAULT_VALUE_WARNING,
tweakId, registerFunc.getName(),
registerFunc.getExpectedTypeName()));
}
} else if (tweakFunc.isGetterFunction()) {
// For getter calls, ensure the correct getter was used.
if (!tweakFunc.isCorrectRegisterFunction(registerFunc)) {
compiler.report(JSError.make(call.sourceName,
call.callNode, TWEAK_WRONG_GETTER_TYPE_WARNING,
tweakFunc.getName(), registerFunc.getName()));
}
}
}
}
/**
* Emits an error for each function call that was found.
*/
void emitUnknownTweakErrors() {
for (TweakFunctionCall call : functionCalls) {
compiler.report(JSError.make(call.sourceName,
call.getIdNode(), UNKNOWN_TWEAK_WARNING, tweakId));
}
}
void addRegisterCall(String sourceName, TweakFunction tweakFunc,
Node callNode, Node defaultValueNode) {
registerCall = new TweakFunctionCall(sourceName, tweakFunc, callNode,
defaultValueNode);
functionCalls.add(registerCall);
}
void addOverrideDefaultValueCall(String sourceName,
TweakFunction tweakFunc, Node callNode, Node defaultValueNode) {
functionCalls.add(new TweakFunctionCall(sourceName, tweakFunc, callNode,
defaultValueNode));
this.defaultValueNode = defaultValueNode;
}
void addGetterCall(String sourceName, TweakFunction tweakFunc,
Node callNode) {
functionCalls.add(new TweakFunctionCall(sourceName, tweakFunc, callNode));
}
boolean isRegistered() {
return registerCall != null;
}
Node getDefaultValueNode() {
Preconditions.checkState(is
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> fnName;
private final AbstractCompiler compiler;
private final CodingConvention codingConvention;
private final JSTypeRegistry typeRegistry;
private final Node errorRoot;
private final String sourceName;
private final Scope scope;
private FunctionContents contents = UnknownFunctionContents.get();
private JSType returnType = null;
private boolean returnTypeInferred = false;
private List<ObjectType> implementedInterfaces = null;
private List<ObjectType> extendedInterfaces = null;
private ObjectType baseType = null;
private ObjectType thisType = null;
private boolean isConstructor = false;
private boolean isInterface = false;
private Node parametersNode = null;
private ImmutableList<String> templateTypeNames = ImmutableList.of();
static final DiagnosticType EXTENDS_WITHOUT_TYPEDEF = DiagnosticType.warning(
"JSC_EXTENDS_WITHOUT_TYPEDEF",
"@extends used without @constructor or @interface for {0}");
static final DiagnosticType EXTENDS_NON_OBJECT = DiagnosticType.warning(
"JSC_EXTENDS_NON_OBJECT",
"{0} @extends non-object type {1}");
static final DiagnosticType RESOLVED_TAG_EMPTY = DiagnosticType.warning(
"JSC_RESOLVED_TAG_EMPTY",
"Could not resolve type in {0} tag of {1}");
static final DiagnosticType IMPLEMENTS_WITHOUT_CONSTRUCTOR =
DiagnosticType.warning(
"JSC_IMPLEMENTS_WITHOUT_CONSTRUCTOR",
"@implements used without @constructor or @interface for {0}");
static final DiagnosticType VAR_ARGS_MUST_BE_LAST = DiagnosticType.warning(
"JSC_VAR_ARGS_MUST_BE_LAST",
"variable length argument must be last");
static final DiagnosticType OPTIONAL_ARG_AT_END = DiagnosticType.warning(
"JSC_OPTIONAL_ARG_AT_END",
"optional arguments must be at the end");
static final DiagnosticType INEXISTANT_PARAM = DiagnosticType.warning(
"JSC_INEXISTANT_PARAM",
"parameter {0} does not appear in {1}''s parameter list");
static final DiagnosticType TYPE_REDEFINITION = DiagnosticType.warning(
"JSC_TYPE_REDEFINITION",
"attempted re-definition of type {0}\n"
+ "found : {1}\n"
+ "expected: {2}");
static final DiagnosticType TEMPLATE_TYPE_DUPLICATED = DiagnosticType.warning(
"JSC_TEMPLATE_TYPE_DUPLICATED",
"Only one parameter type must be the template type");
static final DiagnosticType TEMPLATE_TYPE_EXPECTED = DiagnosticType.warning(
"JSC_TEMPLATE_TYPE_EXPECTED",
"The template type must be a parameter type");
static final DiagnosticType THIS_TYPE_NON_OBJECT =
DiagnosticType.warning(
"JSC_THIS_TYPE_NON_OBJECT",
"@this type of a function must be an object\n" +
"Actual type: {0}");
private class ExtendedTypeValidator implements Predicate<JSType> {
@Override
public boolean apply(JSType type) {
ObjectType objectType = ObjectType.cast(type);
if (objectType == null) {
reportWarning(EXTENDS_NON_OBJECT, fnName, type.toString());
return false;
} else if (objectType.isEmptyType()) {
reportWarning(RESOLVED_TAG_EMPTY, "@extends", fnName);
return false;
} else if (objectType.isUnknownType()) {
if (hasMoreTags
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> }
/**
* Given a qualified name node, returns whether "prototype" is at the end.
* For example:
* a.b.c => false
* a.b.c.prototype => true
*/
private boolean endsWithPrototype(Node qualifiedName) {
return qualifiedName.isGetProp() &&
qualifiedName.getLastChild().getString().equals("prototype");
}
/**
* Extracts X from goog.provide('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfProvide(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.provide");
}
/**
* Extracts X from goog.require('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfRequire(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.require");
}
private static String extractClassNameIfGoog(Node node, Node parent,
String functionName){
String className = null;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.isGetProp()) {
String qualifiedName = callee.getQualifiedName();
if (functionName.equals(qualifiedName)) {
Node target = callee.getNext();
if (target != null && target.isString()) {
className = target.getString();
}
}
}
}
return className;
}
/**
* Use closure's implementation.
* @return closure's function name for exporting properties.
*/
@Override
public String getExportPropertyFunction() {
return "goog.exportProperty";
}
/**
* Use closure's implementation.
* @return closure's function name for exporting symbols.
*/
@Override
public String getExportSymbolFunction() {
return "goog.exportSymbol";
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
Node callName = n.getFirstChild();
if ("goog.addDependency".equals(callName.getQualifiedName()) &&
n.getChildCount() >= 3) {
Node typeArray = callName.getNext().getNext();
if (typeArray.isArrayLit()) {
List<String> typeNames = Lists.newArrayList();
for (Node name = typeArray.getFirstChild(); name != null;
name = name.getNext()) {
if (name.isString()) {
typeNames.add(name.getString());
}
}
return typeNames;
}
}
return super.identifyTypeDeclarationCall(n);
}
@Override
public String getAbstractMethodName() {
return "goog.abstractMethod";
}
@Override
public String getSingletonGetterClassName(Node callNode) {
Node callArg = callNode.getFirstChild();
String callName = callArg.getQualifiedName();
// Use both the original name and the post-CollapseProperties name.
if (!("goog.addSingletonGetter".equals(callName) ||
"goog$addSingletonGetter".equals(callName)) ||
callNode.getChildCount() != 2) {
return super.getSingletonGetterClassName(callNode);
}
return callArg.getNext().getQualifiedName();
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType)
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> = n.getFirstChild();
}
}
if (name != null && name.isQualifiedName()) {
String qualifiedName = name.getQualifiedName();
if (!this.convention.isPrivate(qualifiedName)) {
Visibility visibility = info.getVisibility();
if (!visibility.equals(JSDocInfo.Visibility.PRIVATE)) {
ctors.put(qualifiedName, name);
}
}
}
}
private void visitScriptNode(NodeTraversal t, Node n) {
for (Map.Entry<String, Node> ctorEntry : ctors.entrySet()) {
String ctor = ctorEntry.getKey();
int index = -1;
boolean found = false;
do {
index = ctor.indexOf('.', index +1);
String provideKey = index == -1 ? ctor : ctor.substring(0, index);
if (provides.containsKey(provideKey)) {
found = true;
break;
}
} while (index != -1);
if (!found) {
compiler.report(
t.makeError(ctorEntry.getValue(), checkLevel,
MISSING_PROVIDE_WARNING, ctorEntry.getKey()));
}
}
provides.clear();
ctors.clear();
}
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphNode;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.ArrayDeque;
import java.util.Comparator;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
/**
* This is a compiler pass that computes a control flow graph.
*
*/
final class ControlFlowAnalysis implements Callback, CompilerPass {
/**
* Based roughly on the first few pages of
*
* "Declarative Intraprocedural Flow Analysis of Java Source Code by
* Nilsson-Nyman, Hedin, Magnusson & Ekman",
*
* this pass computes the control flow graph from the AST. However, a full
* attribute grammar is not necessary. We will compute the flow edges with a
* single post order traversal. The "follow()" of a given node will be
* computed recursively in a demand driven fashion.
*
* As of this moment, we are not performing any inter-procedural analysis
* within our framework.
*/
private final AbstractCompiler compiler;
private ControlFlowGraph<Node> cfg;
private Map<Node, Integer> astPosition;
// TODO(nicksantos): should these be node annotations?
private Map<DiGraphNode<Node, Branch>, Integer> nodePriorities;
// We order CFG nodes by by looking at the AST positions.
// CFG nodes that come first lexically should be visited first, because
// they will often be executed first in the source program.
private final Comparator<DiGraphNode<Node, Branch>> priorityComparator =
new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> a, DiGraphNode<Node, Branch> b) {
return astPosition.get(a.getValue()) - astPosition.get(b.getValue());
}
};
private int astPositionCounter;
private int priorityCounter;
private final boolean shouldTraverseFunctions;
private final boolean edgeAnnotations;
// We need to store where we started, in case we aren't doing a flow analysis
// for the whole scope. This happens, for example, when running type inference
// on only the
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> externs.
private Node root;
/*
* This stack captures the structure of nested TRY blocks. The top of the
* stack is the inner most TRY block. A FUNCTION node in this stack implies
* that the handler is determined by the caller of the function at runtime.
*/
private final Deque<Node> exceptionHandler = new ArrayDeque<Node>();
/*
* This map is used to handle the follow of FINALLY. For example:
*
* while(x) {
* try {
* try {
* break;
* } catch (a) {
* } finally {
* foo();
* }
* fooFollow();
* } catch (b) {
* } finally {
* bar();
* }
* barFollow();
* }
* END();
*
* In this case finallyMap will contain a map from:
* first FINALLY -> bar()
* second FINALLY -> END()
*
* When we are connecting foo() and bar() to to their respective follow, we
* must also look up this map and connect:
* foo() -> bar()
* bar() -> END
*/
private final Multimap<Node, Node> finallyMap = HashMultimap.create();
/**
* Constructor.
*
* @param compiler Compiler instance.
* @param shouldTraverseFunctions Whether functions should be traversed (true
* by default).
* @param edgeAnnotations Whether to allow edge annotations. By default,
* only node annotations are allowed.
*/
ControlFlowAnalysis(AbstractCompiler compiler,
boolean shouldTraverseFunctions, boolean edgeAnnotations) {
this.compiler = compiler;
this.shouldTraverseFunctions = shouldTraverseFunctions;
this.edgeAnnotations = edgeAnnotations;
}
ControlFlowGraph<Node> getCfg() {
return cfg;
}
@Override
public void process(Node externs, Node root) {
this.root = root;
astPositionCounter = 0;
astPosition = Maps.newHashMap();
nodePriorities = Maps.newHashMap();
cfg = new AstControlFlowGraph(computeFallThrough(root), nodePriorities,
edgeAnnotations);
NodeTraversal.traverse(compiler, root, this);
astPosition.put(null, ++astPositionCounter); // the implicit return is last.
// Now, generate the priority of nodes by doing a depth-first
// search on the CFG.
priorityCounter = 0;
DiGraphNode<Node, Branch> entry = cfg.getEntry();
prioritizeFromEntryNode(entry);
if (shouldTraverseFunctions) {
// If we're traversing inner functions, we need to rank the
// priority of them too.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
Node value = candidate.getValue();
if (value != null && value.isFunction()) {
Preconditions.checkState(
!nodePriorities.containsKey(candidate) || candidate == entry);
prioritizeFromEntryNode(candidate);
}
}
}
// At this point, all reachable nodes have been given a priority, but
// unreachable nodes have not been given a priority. Put them last.
// Presumably, it doesn't really matter what priority they get, since
// this shouldn't happen in real code.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
if (!nodePrior
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>ities.containsKey(candidate)) {
nodePriorities.put(candidate, ++priorityCounter);
}
}
// Again, the implicit return node is always last.
nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter);
}
/**
* Given an entry node, find all the nodes reachable from that node
* and prioritize them.
*/
private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) {
PriorityQueue<DiGraphNode<Node, Branch>> worklist =
new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator);
worklist.add(entry);
while (!worklist.isEmpty()) {
DiGraphNode<Node, Branch> current = worklist.remove();
if (nodePriorities.containsKey(current)) {
continue;
}
nodePriorities.put(current, ++priorityCounter);
List<DiGraphNode<Node, Branch>> successors =
cfg.getDirectedSuccNodes(current);
for (DiGraphNode<Node, Branch> candidate : successors) {
worklist.add(candidate);
}
}
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
astPosition.put(n, astPositionCounter++);
switch (n.getType()) {
case Token.FUNCTION:
if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) {
exceptionHandler.push(n);
return true;
}
return false;
case Token.TRY:
exceptionHandler.push(n);
return true;
}
/*
* We are going to stop the traversal depending on what the node's parent
* is.
*
* We are only interested in adding edges between nodes that change control
* flow. The most obvious ones are loops and IF-ELSE's. A statement
* transfers control to its next sibling.
*
* In case of an expression tree, there is no control flow within the tree
* even when there are short circuited operators and conditionals. When we
* are doing data flow analysis, we will simply synthesize lattices up the
* expression tree by finding the meet at each expression node.
*
* For example: within a Token.SWITCH, the expression in question does not
* change the control flow and need not to be considered.
*/
if (parent != null) {
switch (parent.getType()) {
case Token.FOR:
// Only traverse the body of the for loop.
return n == parent.getLastChild();
// Skip the conditions.
case Token.IF:
case Token.WHILE:
case Token.WITH:
return n != parent.getFirstChild();
case Token.DO:
return n != parent.getFirstChild().getNext();
// Only traverse the body of the cases
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.LABEL:
return n != parent.getFirstChild();
case Token.FUNCTION:
return n == parent.getFirstChild().getNext().getNext();
case Token.CONTINUE:
case Token.BREAK:
case Token.EXPR_RESULT:
case Token.VAR:
case Token.RETURN:
case Token.THROW:
return false;
case Token.TRY:
/* Just before we are about to visit the second child of the TRY node,
* we know that we will be visiting either the CATCH or the FINALLY.
* In other words, we know that the post order traversal
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> of the TRY
* block has been finished, no more exceptions can be caught by the
* handler at this TRY block and should be taken out of the stack.
*/
if (n == parent.getFirstChild().getNext()) {
Preconditions.checkState(exceptionHandler.peek() == parent);
exceptionHandler.pop();
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.IF:
handleIf(n);
return;
case Token.WHILE:
handleWhile(n);
return;
case Token.DO:
handleDo(n);
return;
case Token.FOR:
handleFor(n);
return;
case Token.SWITCH:
handleSwitch(n);
return;
case Token.CASE:
handleCase(n);
return;
case Token.DEFAULT_CASE:
handleDefault(n);
return;
case Token.BLOCK:
case Token.SCRIPT:
handleStmtList(n);
return;
case Token.FUNCTION:
handleFunction(n);
return;
case Token.EXPR_RESULT:
handleExpr(n);
return;
case Token.THROW:
handleThrow(n);
return;
case Token.TRY:
handleTry(n);
return;
case Token.CATCH:
handleCatch(n);
return;
case Token.BREAK:
handleBreak(n);
return;
case Token.CONTINUE:
handleContinue(n);
return;
case Token.RETURN:
handleReturn(n);
return;
case Token.WITH:
handleWith(n);
return;
case Token.LABEL:
return;
default:
handleStmt(n);
return;
}
}
private void handleIf(Node node) {
Node thenBlock = node.getFirstChild().getNext();
Node elseBlock = thenBlock.getNext();
createEdge(node, Branch.ON_TRUE, computeFallThrough(thenBlock));
if (elseBlock == null) {
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this)); // not taken branch
} else {
createEdge(node, Branch.ON_FALSE, computeFallThrough(elseBlock));
}
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleWhile(Node node) {
// Control goes to the first statement if the condition evaluates to true.
createEdge(node, Branch.ON_TRUE,
computeFallThrough(node.getFirstChild().getNext()));
// Control goes to the follow() if the condition evaluates to false.
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this));
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleDo(Node node) {
// The first edge can be the initial iteration as well as the iterations
// after.
createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild()));
// The edge that leaves the do loop if the condition fails.
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this));
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleFor(Node forNode) {
if (forNode.getChildCount() == 4) {
// We have for (init;
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> cond; iter) { body }
Node init = forNode.getFirstChild();
Node cond = init.getNext();
Node iter = cond.getNext();
Node body = iter.getNext();
// After initialization, we transfer to the FOR which is in charge of
// checking the condition (for the first time).
createEdge(init, Branch.UNCOND, forNode);
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode, this));
// The end of the body will have a unconditional branch to our iter
// (handled by calling computeFollowNode of the last instruction of the
// body. Our iter will jump to the forNode again to another condition
// check.
createEdge(iter, Branch.UNCOND, forNode);
connectToPossibleExceptionHandler(init, init);
connectToPossibleExceptionHandler(forNode, cond);
connectToPossibleExceptionHandler(iter, iter);
} else {
// We have for (item in collection) { body }
Node item = forNode.getFirstChild();
Node collection = item.getNext();
Node body = collection.getNext();
// The collection behaves like init.
createEdge(collection, Branch.UNCOND, forNode);
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode, this));
connectToPossibleExceptionHandler(forNode, collection);
}
}
private void handleSwitch(Node node) {
// Transfer to the first non-DEFAULT CASE. if there are none, transfer
// to the DEFAULT or the EMPTY node.
Node next = getNextSiblingOfType(
node.getFirstChild().getNext(), Token.CASE, Token.EMPTY);
if (next != null) { // Has at least one CASE or EMPTY
createEdge(node, Branch.UNCOND, next);
} else { // Has no CASE but possibly a DEFAULT
if (node.getFirstChild().getNext() != null) {
createEdge(node, Branch.UNCOND, node.getFirstChild().getNext());
} else { // No CASE, no DEFAULT
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleCase(Node node) {
// Case is a bit tricky....First it goes into the body if condition is true.
createEdge(node, Branch.ON_TRUE,
node.getFirstChild().getNext());
// Look for the next CASE, skipping over DEFAULT.
Node next = getNextSiblingOfType(node.getNext(), Token.CASE);
if (next != null) { // Found a CASE
Preconditions.checkState(next.isCase());
createEdge(node, Branch.ON_FALSE, next);
} else { // No more CASE found, go back and search for a DEFAULT.
Node parent = node.getParent();
Node deflt = getNextSiblingOfType(
parent.getFirstChild().getNext(), Token.DEFAULT_CASE);
if (deflt != null) { // Has a DEFAULT
createEdge(node, Branch.ON_FALSE, deflt);
} else { // No DEFAULT found, go to
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> the follow of the SWITCH.
createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleDefault(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleWith(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getLastChild());
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleStmtList(Node node) {
Node parent = node.getParent();
// Special case, don't add a block of empty CATCH block to the graph.
if (node.isBlock() && parent != null &&
parent.isTry() &&
NodeUtil.getCatchBlock(parent) == node &&
!NodeUtil.hasCatchHandler(node)) {
return;
}
// A block transfer control to its first child if it is not empty.
Node child = node.getFirstChild();
// Function declarations are skipped since control doesn't go into that
// function (unless it is called)
while (child != null && child.isFunction()) {
child = child.getNext();
}
if (child != null) {
createEdge(node, Branch.UNCOND, computeFallThrough(child));
} else {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
// Synthetic blocks
if (parent != null) {
switch (parent.getType()) {
case Token.DEFAULT_CASE:
case Token.CASE:
case Token.TRY:
break;
default:
if (node.isBlock() && node.isSyntheticBlock()) {
createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this));
}
break;
}
}
}
private void handleFunction(Node node) {
// A block transfer control to its first child if it is not empty.
Preconditions.checkState(node.getChildCount() >= 3);
createEdge(node, Branch.UNCOND,
computeFallThrough(node.getFirstChild().getNext().getNext()));
Preconditions.checkState(exceptionHandler.peek() == node);
exceptionHandler.pop();
}
private void handleExpr(Node node) {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
private void handleThrow(Node node) {
connectToPossibleExceptionHandler(node, node);
}
private void handleTry(Node node) {
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleCatch(Node node) {
createEdge(node, Branch.UNCOND, node.getLastChild());
}
private void handleBreak(Node node) {
String label = null;
// See if it is a break with label.
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node previous = null;
Node lastJump;
Node parent = node.getParent();
/*
* Continuously look up the ancestor tree for the BREAK target or the target
* with the corresponding label and connect to it. If along the path we
* discover a FINALLY, we will connect the
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> BREAK to that FINALLY. From then
* on, we will just record the control flow changes in the finallyMap. This
* is due to the fact that we need to connect any node that leaves its own
* FINALLY block to the outer FINALLY or the BREAK's target but those nodes
* are not known yet due to the way we traverse the nodes.
*/
for (cur = node, lastJump = node;
!isBreakTarget(cur, label);
cur = parent, parent = parent.getParent()) {
if (cur.isTry() && NodeUtil.hasFinally(cur)
&& cur.getLastChild() != previous) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFallThrough(
cur.getLastChild()));
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
if (parent == null) {
if (compiler.isIdeMode()) {
// In IDE mode, we expect that the data flow graph may
// not be well-formed.
return;
} else {
throw new IllegalStateException("Cannot find break target.");
}
}
previous = cur;
}
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur, this));
} else {
finallyMap.put(lastJump, computeFollowNode(cur, this));
}
}
private void handleContinue(Node node) {
String label = null;
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node previous = null;
Node lastJump;
// Similar to handBreak's logic with a few minor variation.
Node parent = node.getParent();
for (cur = node, lastJump = node;
!isContinueTarget(cur, parent, label);
cur = parent, parent = parent.getParent()) {
if (cur.isTry() && NodeUtil.hasFinally(cur)
&& cur.getLastChild() != previous) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, cur.getLastChild());
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
Preconditions.checkState(parent != null, "Cannot find continue target.");
previous = cur;
}
Node iter = cur;
if (cur.getChildCount() == 4) {
iter = cur.getFirstChild().getNext().getNext();
}
if (lastJump == node) {
createEdge(node, Branch.UNCOND, iter);
} else {
finallyMap.put(lastJump, iter);
}
}
private void handleReturn(Node node) {
Node lastJump = null;
for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext();) {
Node curHandler = iter.next();
if (curHandler.isFunction()) {
break;
}
if (NodeUtil.hasFinally(curHandler)) {
if (lastJump == null) {
createEdge(node, Branch.UNCOND, curHandler.getLastChild());
} else {
finallyMap.put(lastJump,
computeFallThrough(curHandler.getLastChild()));
}
lastJump = curHandler;
}
}
if (node.hasChildren()) {
connectToPossibleExceptionHandler(node, node
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>.getFirstChild());
}
if (lastJump == null) {
createEdge(node, Branch.UNCOND, null);
} else {
finallyMap.put(lastJump, null);
}
}
private void handleStmt(Node node) {
// Simply transfer to the next line.
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
static Node computeFollowNode(Node node, ControlFlowAnalysis cfa) {
return computeFollowNode(node, node, cfa);
}
static Node computeFollowNode(Node node) {
return computeFollowNode(node, node, null);
}
/**
* Computes the follow() node of a given node and its parent. There is a side
* effect when calling this function. If this function computed an edge that
* exists a FINALLY, it'll attempt to connect the fromNode to the outer
* FINALLY according to the finallyMap.
*
* @param fromNode The original source node since {@code node} is changed
* during recursion.
* @param node The node that follow() should compute.
*/
private static Node computeFollowNode(
Node fromNode, Node node, ControlFlowAnalysis cfa) {
/*
* This is the case where:
*
* 1. Parent is null implies that we are transferring control to the end of
* the script.
*
* 2. Parent is a function implies that we are transferring control back to
* the caller of the function.
*
* 3. If the node is a return statement, we should also transfer control
* back to the caller of the function.
*
* 4. If the node is root then we have reached the end of what we have been
* asked to traverse.
*
* In all cases we should transfer control to a "symbolic return" node.
* This will make life easier for DFAs.
*/
Node parent = node.getParent();
if (parent == null || parent.isFunction() ||
(cfa != null && node == cfa.root)) {
return null;
}
// If we are just before a IF/WHILE/DO/FOR:
switch (parent.getType()) {
// The follow() of any of the path from IF would be what follows IF.
case Token.IF:
return computeFollowNode(fromNode, parent, cfa);
case Token.CASE:
case Token.DEFAULT_CASE:
// After the body of a CASE, the control goes to the body of the next
// case, without having to go to the case condition.
if (parent.getNext() != null) {
if (parent.getNext().isCase()) {
return parent.getNext().getFirstChild().getNext();
} else if (parent.getNext().isDefaultCase()) {
return parent.getNext().getFirstChild();
} else {
Preconditions.checkState(false, "Not reachable");
}
} else {
return computeFollowNode(fromNode, parent, cfa);
}
break;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
return parent;
} else {
return parent.getFirstChild().getNext().getNext();
}
case Token.WHILE:
case Token.DO:
return parent;
case Token.TRY:
// If we are coming out of the TRY block...
if (parent.getFirstChild() == node) {
if (
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(parent.getLastChild());
} else { // and have no FINALLY.
return computeFollowNode(fromNode, parent, cfa);
}
// CATCH block.
} else if (NodeUtil.getCatchBlock(parent) == node){
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(node.getNext());
} else {
return computeFollowNode(fromNode, parent, cfa);
}
// If we are coming out of the FINALLY block...
} else if (parent.getLastChild() == node){
if (cfa != null) {
for (Node finallyNode : cfa.finallyMap.get(parent)) {
cfa.createEdge(fromNode, Branch.ON_EX, finallyNode);
}
}
return computeFollowNode(fromNode, parent, cfa);
}
}
// Now that we are done with the special cases follow should be its
// immediate sibling, unless its sibling is a function
Node nextSibling = node.getNext();
// Skip function declarations because control doesn't get pass into it.
while (nextSibling != null && nextSibling.isFunction()) {
nextSibling = nextSibling.getNext();
}
if (nextSibling != null) {
return computeFallThrough(nextSibling);
} else {
// If there are no more siblings, control is transferred up the AST.
return computeFollowNode(fromNode, parent, cfa);
}
}
/**
* Computes the destination node of n when we want to fallthrough into the
* subtree of n. We don't always create a CFG edge into n itself because of
* DOs and FORs.
*/
static Node computeFallThrough(Node n) {
switch (n.getType()) {
case Token.DO:
return computeFallThrough(n.getFirstChild());
case Token.FOR:
if (NodeUtil.isForIn(n)) {
return n.getFirstChild().getNext();
}
return computeFallThrough(n.getFirstChild());
case Token.LABEL:
return computeFallThrough(n.getLastChild());
default:
return n;
}
}
/**
* Connects the two nodes in the control flow graph.
*
* @param fromNode Source.
* @param toNode Destination.
*/
private void createEdge(Node fromNode, ControlFlowGraph.Branch branch,
Node toNode) {
cfg.createNode(fromNode);
cfg.createNode(toNode);
cfg.connectIfNotFound(fromNode, branch, toNode);
}
/**
* Connects cfgNode to the proper CATCH block if target subtree might throw
* an exception. If there are FINALLY blocks reached before a CATCH, it will
* make the corresponding entry in finallyMap.
*/
private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
Node lastJump = cfgNode;
for (Node handler : exceptionHandler) {
if (handler.isFunction()) {
return;
}
Preconditions.checkState(handler.isTry());
Node catchBlock = NodeUtil.getCatchBlock(handler);
if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, handler
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>.getLastChild());
} else {
finallyMap.put(lastJump, handler.getLastChild());
}
} else { // Has a catch.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, catchBlock);
return;
} else {
finallyMap.put(lastJump, catchBlock);
}
}
lastJump = handler;
}
}
}
/**
* Get the next sibling (including itself) of one of the given types.
*/
private static Node getNextSiblingOfType(Node first, int ... types) {
for (Node c = first; c != null; c = c.getNext()) {
for (int type : types) {
if (c.getType() == type) {
return c;
}
}
}
return null;
}
/**
* Checks if target is actually the break target of labeled continue. The
* label can be null if it is an unlabeled break.
*/
public static boolean isBreakTarget(Node target, String label) {
return isBreakStructure(target, label != null) &&
matchLabel(target.getParent(), label);
}
/**
* Checks if target is actually the continue target of labeled continue. The
* label can be null if it is an unlabeled continue.
*/
private static boolean isContinueTarget(
Node target, Node parent, String label) {
return isContinueStructure(target) && matchLabel(parent, label);
}
/**
* Check if label is actually referencing the target control structure. If
* label is null, it always returns true.
*/
private static boolean matchLabel(Node target, String label) {
if (label == null) {
return true;
}
while (target.isLabel()) {
if (target.getFirstChild().getString().equals(label)) {
return true;
}
target = target.getParent();
}
return false;
}
/**
* Determines if the subtree might throw an exception.
*/
public static boolean mayThrowException(Node n) {
switch (n.getType()) {
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.THROW:
case Token.NEW:
case Token.ASSIGN:
case Token.INC:
case Token.DEC:
case Token.INSTANCEOF:
return true;
case Token.FUNCTION:
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) {
return true;
}
}
return false;
}
/**
* Determines whether the given node can be terminated with a BREAK node.
*/
static boolean isBreakStructure(Node n, boolean labeled) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.SWITCH:
return true;
case Token.BLOCK:
case Token.IF:
case Token.TRY:
return labeled;
default:
return false;
}
}
/**
* Determines whether the given node can be advanced with a CONTINUE node.
*/
static boolean isContinueStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
/**
* Get the TRY block with a CATCH that would be run if n throws an exception.
* @return The CATCH node or null if it there isn't a CATCH before the
* the function terminates.
*/
static Node getExceptionHandler(Node n) {
for (Node cur = n;
!cur.isScript() && !cur.isFunction();
cur = cur.getParent()) {
Node catchNode = getCatchHandlerForBlock(cur);
if (catchNode != null) {
return catchNode;
}
}
return null;
}
/**
* Locate the catch BLOCK given the first block in a TRY.
* @return The CATCH node or null there is no catch handler.
*/
static Node getCatchHandlerForBlock(Node block) {
if (block.isBlock() &&
block.getParent().isTry() &&
block.getParent().getFirstChild() == block) {
for (Node s = block.getNext(); s != null; s = s.getNext()) {
if (NodeUtil.hasCatchHandler(s)) {
return s.getFirstChild();
}
}
}
return null;
}
/**
* A {@link ControlFlowGraph} which provides a node comparator based on the
* pre-order traversal of the AST.
*/
private static class AstControlFlowGraph extends ControlFlowGraph<Node> {
private final Map<DiGraphNode<Node, Branch>, Integer> priorities;
/**
* Constructor.
* @param entry The entry node.
* @param priorities The map from nodes to position in the AST (to be
* filled by the {@link ControlFlowAnalysis#shouldTraverse}).
*/
private AstControlFlowGraph(Node entry,
Map<DiGraphNode<Node, Branch>, Integer> priorities,
boolean edgeAnnotations) {
super(entry,
true /* node annotations */, edgeAnnotations);
this.priorities = priorities;
}
@Override
/**
* Returns a node comparator based on the pre-order traversal of the AST.
* @param isForward x 'before' y in the pre-order traversal implies
* x 'less than' y (if true) and x 'greater than' y (if false).
*/
public Comparator<DiGraphNode<Node, Branch>> getOptionalNodeComparator(
boolean isForward) {
if (isForward) {
return new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) {
return getPosition(n1) - getPosition(n2);
}
};
} else {
return new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) {
return getPosition(n2) - getPosition(n1);
}
};
}
}
/**
* Gets the pre-order traversal position of the given node.
* @return An arbitrary counter used for comparing positions.
*/
private int getPosition(DiGraphNode<Node, Branch> n) {
Integer priority = priorities.get(n);
Preconditions.checkNotNull(priority);
return priority;
}
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>keeping.
RecordType maybeRecordType = greatestSubtype.toMaybeRecordType();
if (maybeRecordType != null && maybeRecordType.isSynthetic()) {
continue;
}
return true;
}
}
}
return false;
}
/**
* Returns each type that has a property {@code propertyName} defined on it.
*
* Like most types in our type system, the collection of types returned
* will be collapsed. This means that if a type is defined on
* {@code Object} and on {@code Array}, it would be reasonable for this
* method to return either {@code [Object, Array]} or just {@code [Object]}.
*/
public Iterable<JSType> getTypesWithProperty(String propertyName) {
if (typesIndexedByProperty.containsKey(propertyName)) {
return typesIndexedByProperty.get(propertyName).getAlternates();
} else {
return ImmutableList.of();
}
}
/**
* Returns each reference type that has a property {@code propertyName}
* defined on it.
*
* Unlike most types in our type system, the collection of types returned
* will not be collapsed. This means that if a type is defined on
* {@code Object} and on {@code Array}, this method must return
* {@code [Object, Array]}. It would not be correct to collapse them to
* {@code [Object]}.
*/
public Iterable<ObjectType> getEachReferenceTypeWithProperty(
String propertyName) {
if (eachRefTypeIndexedByProperty.containsKey(propertyName)) {
return eachRefTypeIndexedByProperty.get(propertyName).values();
} else {
return ImmutableList.of();
}
}
/**
* Finds the common supertype of the two given object types.
*/
ObjectType findCommonSuperObject(ObjectType a, ObjectType b) {
List<ObjectType> stackA = getSuperStack(a);
List<ObjectType> stackB = getSuperStack(b);
ObjectType result = getNativeObjectType(JSTypeNative.OBJECT_TYPE);
while (!stackA.isEmpty() && !stackB.isEmpty()) {
ObjectType currentA = stackA.remove(stackA.size() - 1);
ObjectType currentB = stackB.remove(stackB.size() - 1);
if (currentA.isEquivalentTo(currentB)) {
result = currentA;
} else {
return result;
}
}
return result;
}
private static List<ObjectType> getSuperStack(ObjectType a) {
List<ObjectType> stack = Lists.newArrayListWithExpectedSize(5);
for (ObjectType current = a;
current != null;
current = current.getImplicitPrototype()) {
stack.add(current);
}
return stack;
}
/**
* Increments the current generation. Clients must call this in order to
* move to the next generation of type resolution, allowing types to attempt
* resolution again.
*/
public void incrementGeneration() {
for (NamedType type : resolvedNamedTypes.values()) {
type.clearResolved();
}
unresolvedNamedTypes.putAll(resolvedNamedTypes);
resolvedNamedTypes.clear();
}
boolean isLastGeneration() {
return lastGeneration;
}
/**
* Sets whether this is the last generation. In the last generation,
* {@link NamedType} warns about unresolved types.
*/
public void setLastGeneration(boolean lastGeneration) {
this.lastGeneration = lastGeneration;
}
/**
* Tells the type
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>OptionalNullableType(JSType type) {
return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE),
getNativeType(JSTypeNative.NULL_TYPE));
}
/**
* Creates a union type whose variants are the arguments.
*/
public JSType createUnionType(JSType... variants) {
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (JSType type : variants) {
builder.addAlternate(type);
}
return builder.build();
}
/**
* Creates a union type whose variants are the built-in types specified
* by the arguments.
*/
public JSType createUnionType(JSTypeNative... variants) {
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (JSTypeNative typeId : variants) {
builder.addAlternate(getNativeType(typeId));
}
return builder.build();
}
/**
* Creates an enum type.
*/
public EnumType createEnumType(
String name, Node source, JSType elementsType) {
return new EnumType(this, name, source, elementsType);
}
/**
* Creates an arrow type, an abstract representation of the parameters
* and return value of a function.
*
* @param parametersNode the parameters' types, formatted as a Node with
* param names and optionality info.
* @param returnType the function's return type
*/
ArrowType createArrowType(Node parametersNode, JSType returnType) {
return new ArrowType(this, parametersNode, returnType);
}
/**
* Creates an arrow type with an unknown return type.
*
* @param parametersNode the parameters' types, formatted as a Node with
* param names and optionality info.
*/
ArrowType createArrowType(Node parametersNode) {
return new ArrowType(this, parametersNode, null);
}
/**
* Creates a function type.
*
* @param returnType the function's return type
* @param parameterTypes the parameters' types
*/
public FunctionType createFunctionType(
JSType returnType, JSType... parameterTypes) {
return createFunctionType(returnType, createParameters(parameterTypes));
}
/**
* Creates a function type. The last parameter type of the function is
* considered a variable length argument.
*
* @param returnType the function's return type
* @param parameterTypes the parameters' types
*/
public FunctionType createFunctionTypeWithVarArgs(
JSType returnType, List<JSType> parameterTypes) {
return createFunctionType(
returnType, createParametersWithVarArgs(parameterTypes));
}
/**
* Creates a function type.
*
* @param returnType the function's return type
* @param parameterTypes the parameters' types
*/
public FunctionType createFunctionType(
JSType returnType, List<JSType> parameterTypes) {
return createFunctionType(returnType, createParameters(parameterTypes));
}
/**
* Creates a function type. The last parameter type of the function is
* considered a variable length argument.
*
* @param returnType the function's return type
* @param parameterTypes the parameters' types
*/
public FunctionType createFunctionTypeWithVarArgs(
JSType returnType, JSType... parameterTypes) {
return createFunctionType(
returnType, createParametersWithVarArgs(parameterTypes));
}
/**
* Creates a function type. The last parameter type of the function is
* considered a variable length argument.
*
* @param returnType the function's return type
*
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> @param parameterTypes the parameters' types
*/
private FunctionType createNativeFunctionTypeWithVarArgs(
JSType returnType, JSType... parameterTypes) {
return createNativeFunctionType(
returnType, createParametersWithVarArgs(parameterTypes));
}
/**
* Creates a function type which can act as a constructor.
*
* @param returnType the function's return type
* @param parameterTypes the parameters' types
*/
public FunctionType createConstructorType(
JSType returnType, JSType... parameterTypes) {
return createConstructorType(
null, null, createParameters(parameterTypes), returnType);
}
/**
* Creates a function type which can act as a constructor. The last
* parameter type of the constructor is considered a variable length argument.
*
* @param returnType the function's return type
* @param parameterTypes the parameters' types
*/
public FunctionType createConstructorTypeWithVarArgs(
JSType returnType, JSType... parameterTypes) {
return createConstructorType(
null, null, createParametersWithVarArgs(parameterTypes), returnType);
}
/**
* Creates a function type in which {@code this} refers to an object instance.
*
* @param instanceType the type of {@code this}
* @param returnType the function's return type
* @param parameterTypes the parameters' types
*/
public JSType createFunctionType(ObjectType instanceType,
JSType returnType, List<JSType> parameterTypes) {
return new FunctionBuilder(this)
.withParamsNode(createParameters(parameterTypes))
.withReturnType(returnType)
.withTypeOfThis(instanceType)
.build();
}
/**
* Creates a function type in which {@code this} refers to an object instance.
* The last parameter type of the function is considered a variable length
* argument.
*
* @param instanceType the type of {@code this}
* @param returnType the function's return type
* @param parameterTypes the parameters' types
*/
public JSType createFunctionTypeWithVarArgs(ObjectType instanceType,
JSType returnType, List<JSType> parameterTypes) {
return new FunctionBuilder(this)
.withParamsNode(createParametersWithVarArgs(parameterTypes))
.withReturnType(returnType)
.withTypeOfThis(instanceType)
.build();
}
/**
* Creates a tree hierarchy representing a typed argument list.
*
* @param parameterTypes the parameter types.
* @return a tree hierarchy representing a typed argument list.
*/
public Node createParameters(List<JSType> parameterTypes) {
return createParameters(
parameterTypes.toArray(new JSType[parameterTypes.size()]));
}
/**
* Creates a tree hierarchy representing a typed argument list. The last
* parameter type is considered a variable length argument.
*
* @param parameterTypes the parameter types. The last element of this array
* is considered a variable length argument.
* @return a tree hierarchy representing a typed argument list.
*/
public Node createParametersWithVarArgs(List<JSType> parameterTypes) {
return createParametersWithVarArgs(
parameterTypes.toArray(new JSType[parameterTypes.size()]));
}
/**
* Creates a tree hierarchy representing a typed argument list.
*
* @param parameterTypes the parameter types.
* @return a tree hierarchy representing a typed argument list.
*/
public Node createParameters(JSType... parameterTypes) {
return createParameters(false, parameterTypes);
}
/**
* Creates a tree hierarchy representing
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
boolean addSuccess = paramBuilder.addOptionalParams(type);
if (!addSuccess) {
reporter.warning(
ScriptRuntime.getMessage0("msg.jsdoc.function.varargs"),
sourceName, arg.getLineno(), arg.getCharno());
}
} else {
paramBuilder.addRequiredParams(type);
}
}
}
current = current.getNext();
}
JSType returnType =
createFromTypeNodesInternal(current, sourceName, scope);
return new FunctionBuilder(this)
.withParams(paramBuilder)
.withReturnType(returnType)
.withTypeOfThis(thisType)
.setIsConstructor(isConstructor)
.build();
}
throw new IllegalStateException(
"Unexpected node in type expression: " + n.toString());
}
/**
* Creates a RecordType from the nodes representing said record type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
private JSType createRecordTypeFromNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
RecordTypeBuilder builder = new RecordTypeBuilder(this);
// For each of the fields in the record type.
for (Node fieldTypeNode = n.getFirstChild();
fieldTypeNode != null;
fieldTypeNode = fieldTypeNode.getNext()) {
// Get the property's name.
Node fieldNameNode = fieldTypeNode;
boolean hasType = false;
if (fieldTypeNode.getType() == Token.COLON) {
fieldNameNode = fieldTypeNode.getFirstChild();
hasType = true;
}
String fieldName = fieldNameNode.getString();
// TODO(user): Move this into the lexer/parser.
// Remove the string literal characters around a field name,
// if any.
if (fieldName.startsWith("'") || fieldName.startsWith("\"")) {
fieldName = fieldName.substring(1, fieldName.length() - 1);
}
// Get the property's type.
JSType fieldType = null;
if (hasType) {
// We have a declared type.
fieldType = createFromTypeNodesInternal(
fieldTypeNode.getLastChild(), sourceName, scope);
} else {
// Otherwise, the type is UNKNOWN.
fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
// Add the property to the record.
if (builder.addProperty(fieldName, fieldType, fieldNameNode) == null) {
// Duplicate field name, warning and skip
reporter.warning(
"Duplicate record field " + fieldName,
sourceName,
n.getLineno(), fieldNameNode.getCharno());
}
}
return builder.build();
}
/**
* Sets the template type name.
*/
public void setTemplateTypeNames(List<String> names) {
Preconditions.checkNotNull(names);
for (String name : names) {
templateTypes.put(name, new TemplateType(this, name));
}
}
/**
* Clears the template type name.
*/
public void clearTemplateTypeNames() {
templateTypes.clear();
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>} if a template type name was already defined.
*/
public boolean recordTemplateTypeNames(List<String> names) {
if (currentInfo.declareTemplateTypeNames(names)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records a thrown type.
*/
public boolean recordThrowType(JSTypeExpression type) {
if (!hasAnySingletonTypeTags()) {
currentInfo.declareThrows(type);
populated = true;
return true;
}
return false;
}
/**
* Records a throw type's description.
*
* @return {@code true} if the type's description was recorded and
* {@code false} if a description with the same type was already defined
*/
public boolean recordThrowDescription(
JSTypeExpression type, String description) {
if (currentInfo.documentThrows(type, description)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Adds an author to the current information.
*/
public boolean addAuthor(String author) {
if (currentInfo.documentAuthor(author)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Adds a reference ("@see") to the current information.
*/
public boolean addReference(String reference) {
if (currentInfo.documentReference(reference)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isConsistentIdGenerator()} flag set to
* {@code true}.
*
* @return {@code true} if the consistentIdGenerator flag was recorded and
* {@code false} if it was already recorded
*/
public boolean recordConsistentIdGenerator() {
if (!currentInfo.isConsistentIdGenerator()) {
currentInfo.setConsistentIdGenerator(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records the version.
*/
public boolean recordVersion(String version) {
if (currentInfo.documentVersion(version)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records the deprecation reason.
*/
public boolean recordDeprecationReason(String reason) {
if (currentInfo.setDeprecationReason(reason)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records the list of suppressed warnings.
*/
public boolean recordSuppressions(Set<String> suppressions) {
if (currentInfo.setSuppressions(suppressions)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records the list of modifies warnings.
*/
public boolean recordModifies(Set<String> modifies) {
if (!hasAnySingletonSideEffectTags()
&& currentInfo.setModifies(modifies)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records a type.
*
* @return {@code true} if the type was recorded and {@code false} if
* it is invalid or was already defined
*/
public boolean recordType(JSTypeExpression type) {
if
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.jscomp.graph.LinkedDirectedGraph;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Comparator;
/**
* Control flow graph.
*
*
* @param <N> The instruction type of the control flow graph.
*/
class ControlFlowGraph<N> extends
LinkedDirectedGraph<N, ControlFlowGraph.Branch> {
/**
* A special node marked by the node value key null to a singleton
* "return" when control is transferred outside of the current control flow
* graph.
*/
private final DiGraphNode<N, ControlFlowGraph.Branch> implicitReturn;
private final DiGraphNode<N, ControlFlowGraph.Branch> entry;
/**
* Constructor.
*/
ControlFlowGraph(
N entry, boolean nodeAnnotations, boolean edgeAnnotations) {
super(nodeAnnotations, edgeAnnotations);
implicitReturn = createDirectedGraphNode(null);
this.entry = createDirectedGraphNode(entry);
}
/**
* Gets the implicit return node.
*
* @return Return node.
*/
public DiGraphNode<N, ControlFlowGraph.Branch> getImplicitReturn() {
return implicitReturn;
}
/**
* Gets the entry point of the control flow graph. In general, this should be
* the beginning of the global script or beginning of a function.
*
* @return The entry point.
*/
public DiGraphNode<N, ControlFlowGraph.Branch> getEntry() {
return entry;
}
/**
* Checks whether node is the implicit return.
*
* @param node Node.
* @return True if the node is the implicit return.
*/
public boolean isImplicitReturn(
DiGraphNode<N, ControlFlowGraph.Branch> node) {
return node == implicitReturn;
}
/**
* Connects the node to the explicit return.
*
* @param srcValue Node.
* @param edgeValue Edge.
*/
public void connectToImplicitReturn(N srcValue, Branch edgeValue) {
super.connect(srcValue, edgeValue, null);
}
/**
* Gets a comparator for the nodes. The default implementation returns
* {@code null}. See {@link ControlFlowGraph#getOptionalNodeComparator}.
* @param isForward Whether the comparator sorts the nodes in the direction of
* the flow.
* @return a comparator or null (in particular, if not overridden)
*/
public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator(
boolean isForward) {
return null;
}
/**
* The edge object
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> for the control flow graph.
*/
public static enum Branch {
/** Edge is taken if the condition is true. */
ON_TRUE,
/** Edge is taken if the condition is false. */
ON_FALSE,
/** Unconditional branch. */
UNCOND,
/**
* Exception-handling code paths.
* Conflates two kind of control flow passing:
* - An exception is thrown, and falls into a catch or finally block
* - During exception handling, a finally block finishes and control
* passes to the next finally block.
* In theory, we need 2 different edge types. In practice, we
* can just treat them as "the edges we can't really optimize".
*/
ON_EX,
/** Possible folded-away template */
SYN_BLOCK;
public boolean isConditional() {
return this == ON_TRUE || this == ON_FALSE;
}
}
/**
* Abstract callback to visit a control flow graph node without going into
* subtrees of the node that is also represented by another control flow graph
* node.
*
* <p>For example, traversing an IF node as root will visit the two subtree
* pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and
* {@link ControlFlowGraph.Branch#ON_FALSE} edge.
*/
public abstract static class AbstractCfgNodeTraversalCallback implements
Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (parent == null) {
return true;
}
return !isEnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.SCRIPT:
case Token.TRY:
return true;
case Token.FUNCTION:
// A function node represents the start of a function where the name
// is bleed into the local scope and parameters has been assigned
// to the formal argument names. The node includes the name of the
// function and the LP list since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body represent by the second
// child.
return n != parent.getFirstChild().getNext();
case Token.WHILE:
case Token.DO:
case Token.IF:
// Theses control structure is represented by its node that holds the
// condition. Each of them is a branch node based on its condition.
return NodeUtil.getConditionExpression(parent) != n;
case Token.FOR:
// The FOR(;;) node differs from other control structure in that
// it has a initialization and a increment statement. Those
// two statements have its corresponding CFG nodes to represent them.
// The FOR node represents the condition check for each iteration.
// That way the following:
// for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to
// var x = 0; while(x<10) { x++; }
if (NodeUtil.isForIn(parent)) {
// TODO(user): Investigate how we should handle the case where
// we have a very complex expression inside the FOR-IN header.
return n != parent.getFirst
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> If visibility is not specified, we just assume that visibility
// is inherited from the super class.
INHERITED
}
private static final class LazilyInitializedInfo implements Serializable {
private static final long serialVersionUID = 1L;
// Function information
JSTypeExpression baseType = null;
List<JSTypeExpression> extendedInterfaces = null;
List<JSTypeExpression> implementedInterfaces = null;
Map<String, JSTypeExpression> parameters = null;
List<JSTypeExpression> thrownTypes = null;
ImmutableList<String> templateTypeNames = null;
// Other information
String description = null;
String meaning = null;
String deprecated = null;
String license = null;
Set<String> suppressions = null;
Set<String> modifies = null;
String lendsName = null;
}
private static final class LazilyInitializedDocumentation {
String sourceComment = null;
List<Marker> markers = null;
Map<String, String> parameters = null;
Map<JSTypeExpression, String> throwsDescriptions = null;
String blockDescription = null;
String fileOverview = null;
String returnDescription = null;
String version = null;
List<String> authors = null;
List<String> sees = null;
}
/**
* A piece of information (found in a marker) which contains a position
* with a string.
*/
public static class StringPosition extends SourcePosition<String> {
}
/**
* A piece of information (found in a marker) which contains a position
* with a string that has no leading or trailing whitespace.
*/
static class TrimmedStringPosition extends StringPosition {
@Override public void setItem(String item) {
Preconditions.checkArgument(
item.charAt(0) != ' ' &&
item.charAt(item.length() - 1) != ' ',
"String has leading or trailing whitespace");
super.setItem(item);
}
}
/**
* A piece of information (found in a marker) which contains a position
* with a name node.
*/
public static class NamePosition extends SourcePosition<Node> {}
/**
* A piece of information (found in a marker) which contains a position
* with a type expression syntax tree.
*/
public static class TypePosition extends SourcePosition<Node> {
private boolean brackets = false;
/** Returns whether the type has curly braces around it. */
public boolean hasBrackets() {
return brackets;
}
void setHasBrackets(boolean newVal) {
brackets = newVal;
}
}
/**
* Defines a class for containing the parsing information
* for this JSDocInfo. For each annotation found in the
* JsDoc, a marker will be created indicating the annotation
* itself, the name of the annotation (if any; for example,
* a @param has a name, but a @return does not), the
* textual description found on that annotation and, if applicable,
* the type declaration. All this information is only collected
* if documentation collection is turned on.
*/
public static final class Marker {
private TrimmedStringPosition annotation = null;
private TrimmedStringPosition name = null;
private SourcePosition<Node> nameNode = null;
private StringPosition description = null;
private TypePosition type = null;
/**
* Gets the position information for the annotation name. (e.g., "param")
*/
public StringPosition getAnnotation() {
return annotation;
}
void setAnnotation
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>TypeNames the template type name.
*/
boolean declareTemplateTypeNames(List<String> templateTypeNames) {
lazyInitInfo();
if (info.templateTypeNames != null) {
return false;
}
info.templateTypeNames = ImmutableList.copyOf(templateTypeNames);
return true;
}
/**
* Declares that the method throws a given type.
*
* @param jsType The type that can be thrown by the method.
*/
boolean declareThrows(JSTypeExpression jsType) {
lazyInitInfo();
if (info.thrownTypes == null) {
info.thrownTypes = Lists.newArrayList();
}
info.thrownTypes.add(jsType);
return true;
}
/**
* Gets the visibility specified by {@code @private}, {@code @protected} or
* {@code @public} annotation. If no visibility is specified, visibility
* is inherited from the base class.
*/
public Visibility getVisibility() {
return visibility;
}
/**
* Gets the parameter type.
* @param parameter the parameter's name
* @return the parameter's type or {@code null} if this parameter is not
* defined or has a {@code null} type
*/
public JSTypeExpression getParameterType(String parameter) {
if (info == null || info.parameters == null) {
return null;
}
return info.parameters.get(parameter);
}
/**
* Returns whether the parameter is defined.
*/
public boolean hasParameter(String parameter) {
if (info == null || info.parameters == null) {
return false;
}
return info.parameters.containsKey(parameter);
}
/**
* Returns whether the parameter has an attached type.
*
* @return {@code true} if the parameter has an attached type, {@code false}
* if the parameter has no attached type or does not exist.
*/
public boolean hasParameterType(String parameter) {
return getParameterType(parameter) != null;
}
/**
* Returns the set of names of the defined parameters. The iteration order
* of the returned set is not the order in which parameters are defined.
*
* @return the set of names of the defined parameters. The returned set is
* immutable.
*/
public Set<String> getParameterNames() {
if (info == null || info.parameters == null) {
return ImmutableSet.of();
}
return ImmutableSet.copyOf(info.parameters.keySet());
}
/**
* Gets the number of parameters defined.
*/
public int getParameterCount() {
if (info == null || info.parameters == null) {
return 0;
}
return info.parameters.size();
}
void setType(JSTypeExpression type) {
setType(type, TYPEFIELD_TYPE);
}
void setReturnType(JSTypeExpression type) {
setType(type, TYPEFIELD_RETURN);
}
void setEnumParameterType(JSTypeExpression type) {
setType(type, TYPEFIELD_ENUM);
}
void setTypedefType(JSTypeExpression type) {
setType(type, TYPEFIELD_TYPEDEF);
}
private void setType(JSTypeExpression type, int mask) {
if ((bitset & MASK_TYPEFIELD) != 0) {
throw new IllegalStateException(
"API tried to add two incompatible type tags. " +
"This should have been blocked and emitted a warning.");
}
this.bitset =
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> (bitset & MASK_FLAGS) | mask;
this.type = type;
}
/**
* Returns the list of thrown types.
*/
public List<JSTypeExpression> getThrownTypes() {
if (info == null || info.thrownTypes == null) {
return ImmutableList.of();
}
return Collections.unmodifiableList(info.thrownTypes);
}
/**
* Returns whether a type, specified using the {@code @type} annotation, is
* present on this JSDoc.
*/
public boolean hasType() {
return hasType(TYPEFIELD_TYPE);
}
/**
* Returns whether an enum parameter type, specified using the {@code @enum}
* annotation, is present on this JSDoc.
*/
public boolean hasEnumParameterType() {
return hasType(TYPEFIELD_ENUM);
}
/**
* Returns whether a typedef parameter type, specified using the
* {@code @typedef} annotation, is present on this JSDoc.
*/
public boolean hasTypedefType() {
return hasType(TYPEFIELD_TYPEDEF);
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @return}
* annotation.
*/
public boolean hasReturnType() {
return hasType(TYPEFIELD_RETURN);
}
private boolean hasType(int mask) {
return (bitset & MASK_TYPEFIELD) == mask;
}
/**
* Gets the type specified by the {@code @type} annotation.
*/
public JSTypeExpression getType() {
return getType(TYPEFIELD_TYPE);
}
/**
* Gets the return type specified by the {@code @return} annotation.
*/
public JSTypeExpression getReturnType() {
return getType(TYPEFIELD_RETURN);
}
/**
* Gets the enum parameter type specified by the {@code @enum} annotation.
*/
public JSTypeExpression getEnumParameterType() {
return getType(TYPEFIELD_ENUM);
}
/**
* Gets the typedef type specified by the {@code @type} annotation.
*/
public JSTypeExpression getTypedefType() {
return getType(TYPEFIELD_TYPEDEF);
}
private JSTypeExpression getType(int typefield) {
if ((MASK_TYPEFIELD & bitset) == typefield) {
return type;
} else {
return null;
}
}
/**
* Gets the type specified by the {@code @this} annotation.
*/
public JSTypeExpression getThisType() {
return thisType;
}
/**
* Sets the type specified by the {@code @this} annotation.
*/
void setThisType(JSTypeExpression type) {
this.thisType = type;
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @this}
* annotation.
*/
public boolean hasThisType() {
return thisType != null;
}
void setBaseType(JSTypeExpression type) {
lazyInitInfo();
info.baseType = type;
}
/**
* Gets the base type specified by the {@code @extends} annotation.
*/
public JSTypeExpression getBaseType() {
return (info == null) ? null : info.baseType;
}
/**
* Gets the description specified by the {@code @desc} annotation.
*/
public String getDescription() {
return (info == null) ? null : info.description;
}
void setDescription(String desc) {
lazyInitInfo();
info.description
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> = desc;
}
/**
* Gets the meaning specified by the {@code @meaning} annotation.
*
* In localization systems, two messages with the same content but
* different "meanings" may be translated differently. By default, we
* use the name of the variable that the message is initialized to as
* the "meaning" of the message.
*
* But some code generators (like Closure Templates) inject their own
* meaning with the jsdoc {@code @meaning} annotation.
*/
public String getMeaning() {
return (info == null) ? null : info.meaning;
}
void setMeaning(String meaning) {
lazyInitInfo();
info.meaning = meaning;
}
/**
* Gets the name we're lending to in a {@code @lends} annotation.
*
* In many reflection APIs, you pass an anonymous object to a function,
* and that function mixes the anonymous object into another object.
* The {@code @lends} annotation allows the type system to track
* those property assignments.
*/
public String getLendsName() {
return (info == null) ? null : info.lendsName;
}
void setLendsName(String name) {
lazyInitInfo();
info.lendsName = name;
}
/**
* Gets the description specified by the {@code @license} annotation.
*/
public String getLicense() {
return (info == null) ? null : info.license;
}
/** License directives can appear in multiple comments, and always
* apply to the entire file. Break protection and allow outsiders to
* update the license string so that we can attach the license text even
* when the JSDocInfo has been created and tagged with other information.
* @param license String containing new license text.
*/
public void setLicense(String license) {
lazyInitInfo();
info.license = license;
}
@Override
public String toString() {
return "JSDocInfo";
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @extends}
* annotation.
*/
public boolean hasBaseType() {
return getBaseType() != null;
}
/**
* Adds an implemented interface. Returns whether the interface was added. If
* the interface was already present in the list, it won't get added again.
*/
boolean addImplementedInterface(JSTypeExpression interfaceName) {
lazyInitInfo();
if (info.implementedInterfaces == null) {
info.implementedInterfaces = Lists.newArrayListWithCapacity(2);
}
if (info.implementedInterfaces.contains(interfaceName)) {
return false;
}
info.implementedInterfaces.add(interfaceName);
return true;
}
/**
* Returns the types specified by the {@code @implements} annotation.
*
* @return An immutable list of JSTypeExpression objects that can
* be resolved to types.
*/
public List<JSTypeExpression> getImplementedInterfaces() {
if (info == null || info.implementedInterfaces == null) {
return ImmutableList.of();
}
return Collections.unmodifiableList(info.implementedInterfaces);
}
/**
* Gets the number of interfaces specified by the {@code @implements}
* annotation.
*/
public int getImplementedInterfaceCount() {
if (info == null || info.implementedInterfaces == null) {
return 0;
}
return info.implementedInterfaces.size();
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>
}
/**
* Adds an extended interface (for interface only).
* Returns whether the type was added.
* if the type was already present in the list, it won't get added again.
*/
boolean addExtendedInterface(JSTypeExpression type) {
lazyInitInfo();
if (info.extendedInterfaces == null) {
info.extendedInterfaces = Lists.newArrayListWithCapacity(2);
}
if (info.extendedInterfaces.contains(type)) {
return false;
}
info.extendedInterfaces.add(type);
return true;
}
/**
* Returns the interfaces extended by an interface
*
* @return An immutable list of JSTypeExpression objects that can
* be resolved to types.
*/
public List<JSTypeExpression> getExtendedInterfaces() {
if (info == null || info.extendedInterfaces == null) {
return ImmutableList.of();
}
return Collections.unmodifiableList(info.extendedInterfaces);
}
/**
* Gets the number of extended interfaces specified
*/
public int getExtendedInterfacesCount() {
if (info == null || info.extendedInterfaces == null) {
return 0;
}
return info.extendedInterfaces.size();
}
/**
* Returns the deprecation reason or null if none specified.
*/
public String getDeprecationReason() {
return info == null ? null : info.deprecated;
}
/**
* Returns the set of suppressed warnings.
*/
public Set<String> getSuppressions() {
Set<String> suppressions = info == null ? null : info.suppressions;
return suppressions == null ? Collections.<String>emptySet() : suppressions;
}
/**
* Returns the set of sideeffect notations.
*/
public Set<String> getModifies() {
Set<String> modifies = info == null ? null : info.modifies;
return modifies == null ? Collections.<String>emptySet() : modifies;
}
/**
* Returns whether a description exists for the parameter with the specified
* name.
*/
public boolean hasDescriptionForParameter(String name) {
if (documentation == null || documentation.parameters == null) {
return false;
}
return documentation.parameters.containsKey(name);
}
/**
* Returns the description for the parameter with the given name, if its
* exists.
*/
public String getDescriptionForParameter(String name) {
if (documentation == null || documentation.parameters == null) {
return null;
}
return documentation.parameters.get(name);
}
/**
* Returns the list of authors or null if none.
*/
public Collection<String> getAuthors() {
return documentation == null ? null : documentation.authors;
}
/**
* Returns the list of references or null if none.
*/
public Collection<String> getReferences() {
return documentation == null ? null : documentation.sees;
}
/**
* Returns the version or null if none.
*/
public String getVersion() {
return documentation == null ? null : documentation.version;
}
/**
* Returns the description of the returned object or null if none specified.
*/
public String getReturnDescription() {
return documentation == null ? null : documentation.returnDescription;
}
/**
* Returns the block-level description or null if none specified.
*/
public String getBlockDescription() {
return documentation == null ? null : documentation.blockDescription;
}
/**
* Returns whether this has a fileoverview
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> flag.
*/
public boolean hasFileOverview() {
return getFlag(MASK_FILEOVERVIEW);
}
/**
* Returns the file overview or null if none specified.
*/
public String getFileOverview() {
return documentation == null ? null : documentation.fileOverview;
}
public Node getAssociatedNode() {
return this.associatedNode;
}
/**
* Sets the node associated with this JSDoc.
* Notice that many nodes may have pointer to the same JSDocInfo
* object (because we propagate it across the type graph). But there
* is only one canonical "owner" node of the JSDocInfo, which corresponds
* to its original place in the syntax tree.
*/
public void setAssociatedNode(Node node) {
this.associatedNode = node;
}
/** Gets the name of the source file that contains this JSDoc. */
public String getSourceName() {
return this.associatedNode != null
? this.associatedNode.getSourceFileName() : null;
}
/** Gets the list of all markers for the documentation in this JSDoc. */
public Collection<Marker> getMarkers() {
return (documentation == null || documentation.markers == null)
? ImmutableList.<Marker>of() : documentation.markers;
}
/** Gets the template type name. */
public ImmutableList<String> getTemplateTypeNames() {
if (info == null || info.templateTypeNames == null) {
return ImmutableList.of();
}
return info.templateTypeNames;
}
/**
* Returns a collection of all type nodes that are a part of this JSDocInfo.
* This includes @type, @this, @extends, @implements, @param, @throws,
* and @return. Any future type specific JSDoc should make sure to add the
* appropriate nodes here.
* @return collection of all type nodes
*/
public Collection<Node> getTypeNodes() {
List<Node> nodes = Lists.newArrayList();
if (type != null) {
nodes.add(type.getRoot());
}
if (thisType != null) {
nodes.add(thisType.getRoot());
}
if (info != null) {
if (info.baseType != null) {
nodes.add(info.baseType.getRoot());
}
if (info.extendedInterfaces != null) {
for (JSTypeExpression interfaceType : info.extendedInterfaces) {
nodes.add(interfaceType.getRoot());
}
}
if (info.implementedInterfaces != null) {
for (JSTypeExpression interfaceType : info.implementedInterfaces) {
nodes.add(interfaceType.getRoot());
}
}
if (info.parameters != null) {
for (JSTypeExpression parameterType : info.parameters.values()) {
if (parameterType != null) {
nodes.add(parameterType.getRoot());
}
}
}
if (info.thrownTypes != null) {
for (JSTypeExpression thrownType : info.thrownTypes) {
if (thrownType != null) {
nodes.add(thrownType.getRoot());
}
}
}
}
return nodes;
}
public boolean hasModifies() {
return info != null && info.modifies != null;
}
/**
* Returns the original JSDoc comment string. Returns null unless
* parseJsDocDocumentation is enabled via the ParserConfig.
*/
public String getOriginalCommentString() {
return documentation == null ?
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> literal or an enum");
static final DiagnosticType CTOR_INITIALIZER =
DiagnosticType.warning(
"JSC_CTOR_INITIALIZER_NOT_CTOR",
"Constructor {0} must be initialized at declaration");
static final DiagnosticType IFACE_INITIALIZER =
DiagnosticType.warning(
"JSC_IFACE_INITIALIZER_NOT_IFACE",
"Interface {0} must be initialized at declaration");
static final DiagnosticType CONSTRUCTOR_EXPECTED =
DiagnosticType.warning(
"JSC_REFLECT_CONSTRUCTOR_EXPECTED",
"Constructor expected as first argument");
static final DiagnosticType UNKNOWN_LENDS =
DiagnosticType.warning(
"JSC_UNKNOWN_LENDS",
"Variable {0} not declared before @lends annotation.");
static final DiagnosticType LENDS_ON_NON_OBJECT =
DiagnosticType.warning(
"JSC_LENDS_ON_NON_OBJECT",
"May only lend properties to object types. {0} has type {1}.");
private final AbstractCompiler compiler;
private final ErrorReporter typeParsingErrorReporter;
private final TypeValidator validator;
private final CodingConvention codingConvention;
private final JSTypeRegistry typeRegistry;
private final List<ObjectType> delegateProxyPrototypes = Lists.newArrayList();
private final Map<String, String> delegateCallingConventions =
Maps.newHashMap();
// Simple properties inferred about functions.
private final Map<Node, AstFunctionContents> functionAnalysisResults =
Maps.newHashMap();
/**
* Defer attachment of types to nodes until all type names
* have been resolved. Then, we can resolve the type and attach it.
*/
private class DeferredSetType {
final Node node;
final JSType type;
DeferredSetType(Node node, JSType type) {
Preconditions.checkNotNull(node);
Preconditions.checkNotNull(type);
this.node = node;
this.type = type;
// Other parts of this pass may read off the node.
// (like when we set the LHS of an assign with a typed RHS function.)
node.setJSType(type);
}
void resolve(Scope scope) {
node.setJSType(type.resolve(typeParsingErrorReporter, scope));
}
}
TypedScopeCreator(AbstractCompiler compiler) {
this(compiler, compiler.getCodingConvention());
}
TypedScopeCreator(AbstractCompiler compiler,
CodingConvention codingConvention) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.codingConvention = codingConvention;
this.typeRegistry = compiler.getTypeRegistry();
this.typeParsingErrorReporter = typeRegistry.getErrorReporter();
}
/**
* Creates a scope with all types declared. Declares newly discovered types
* and type properties in the type registry.
*/
@Override
public Scope createScope(Node root, Scope parent) {
// Constructing the global scope is very different than constructing
// inner scopes, because only global scopes can contain named classes that
// show up in the type registry.
Scope newScope = null;
AbstractScopeBuilder scopeBuilder = null;
if (parent == null) {
// Run a first-order analysis over the syntax tree.
(new FirstOrderFunctionAnalyzer(compiler, functionAnalysisResults))
.process(root.getFirstChild(), root.getLastChild());
// Find all the classes in the global scope.
newScope = createInitialScope(root);
GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>);
scopeBuilder = globalScopeBuilder;
NodeTraversal.traverse(compiler, root, scopeBuilder);
} else {
newScope = new Scope(parent, root);
LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope);
scopeBuilder = localScopeBuilder;
localScopeBuilder.build();
}
scopeBuilder.resolveStubDeclarations();
scopeBuilder.resolveTypes();
// Gather the properties in each function that we found in the
// global scope, if that function has a @this type that we can
// build properties on.
for (Node functionNode : scopeBuilder.nonExternFunctions) {
JSType type = functionNode.getJSType();
if (type != null && type.isFunctionType()) {
FunctionType fnType = type.toMaybeFunctionType();
ObjectType fnThisType = fnType.getTypeOfThis();
if (!fnThisType.isUnknownType()) {
NodeTraversal.traverse(compiler, functionNode.getLastChild(),
scopeBuilder.new CollectProperties(fnThisType));
}
}
}
if (parent == null) {
codingConvention.defineDelegateProxyPrototypeProperties(
typeRegistry, newScope, delegateProxyPrototypes,
delegateCallingConventions);
}
return newScope;
}
/**
* Patches a given global scope by removing variables previously declared in
* a script and re-traversing a new version of that script.
*
* @param globalScope The global scope generated by {@code createScope}.
* @param scriptRoot The script that is modified.
*/
void patchGlobalScope(Scope globalScope, Node scriptRoot) {
// Preconditions: This is supposed to be called only on (named) SCRIPT nodes
// and a global typed scope should have been generated already.
Preconditions.checkState(scriptRoot.isScript());
Preconditions.checkNotNull(globalScope);
Preconditions.checkState(globalScope.isGlobal());
String scriptName = NodeUtil.getSourceName(scriptRoot);
Preconditions.checkNotNull(scriptName);
for (Node node : ImmutableList.copyOf(functionAnalysisResults.keySet())) {
if (scriptName.equals(NodeUtil.getSourceName(node))) {
functionAnalysisResults.remove(node);
}
}
(new FirstOrderFunctionAnalyzer(
compiler, functionAnalysisResults)).process(null, scriptRoot);
// TODO(bashir): Variable declaration is not the only side effect of last
// global scope generation but here we only wipe that part off!
// Remove all variables that were previously declared in this scripts.
// First find all vars to remove then remove them because of iterator!
Iterator<Var> varIter = globalScope.getVars();
List<Var> varsToRemove = Lists.newArrayList();
while (varIter.hasNext()) {
Var oldVar = varIter.next();
if (scriptName.equals(oldVar.getInputName())) {
varsToRemove.add(oldVar);
}
}
for (Var var : varsToRemove) {
globalScope.undeclare(var);
globalScope.getTypeOfThis().removeProperty(var.getName());
}
// Now re-traverse the given script.
GlobalScopeBuilder scopeBuilder = new GlobalScopeBuilder(globalScope);
NodeTraversal.traverse(compiler, scriptRoot, scopeBuilder);
}
/**
* Create the outermost scope. This scope contains native binding such as
* {@code Object}, {@code Date}, etc.
*/
@VisibleForTesting
Scope createInitialScope(Node root) {
NodeTraversal.traverse(
compiler,
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
}
}
}
private JSType getNativeType(JSTypeNative nativeType) {
return typeRegistry.getNativeType(nativeType);
}
private abstract class AbstractScopeBuilder
implements NodeTraversal.Callback {
/**
* The scope that we're building.
*/
final Scope scope;
private final List<DeferredSetType> deferredSetTypes =
Lists.newArrayList();
/**
* Functions that we found in the global scope and not in externs.
*/
private final List<Node> nonExternFunctions = Lists.newArrayList();
/**
* Object literals with a @lends annotation aren't analyzed until we
* reach the root of the statement they're defined in.
*
* This ensures that if there are any @lends annotations on the object
* literals, the type on the @lends annotation resolves correctly.
*
* For more information, see
* http://code.google.com/p/closure-compiler/issues/detail?id=314
*/
private List<Node> lentObjectLiterals = null;
/**
* Type-less stubs.
*
* If at the end of traversal, we still don't have types for these
* stubs, then we should declare UNKNOWN types.
*/
private final List<StubDeclaration> stubDeclarations =
Lists.newArrayList();
/**
* The current source file that we're in.
*/
private String sourceName = null;
/**
* The InputId of the current node.
*/
private InputId inputId;
private AbstractScopeBuilder(Scope scope) {
this.scope = scope;
}
void setDeferredType(Node node, JSType type) {
deferredSetTypes.add(new DeferredSetType(node, type));
}
void resolveTypes() {
// Resolve types and attach them to nodes.
for (DeferredSetType deferred : deferredSetTypes) {
deferred.resolve(scope);
}
// Resolve types and attach them to scope slots.
Iterator<Var> vars = scope.getVars();
while (vars.hasNext()) {
vars.next().resolveType(typeParsingErrorReporter);
}
// Tell the type registry that any remaining types
// are unknown.
typeRegistry.resolveTypesInScope(scope);
}
@Override
public final boolean shouldTraverse(NodeTraversal t, Node n,
Node parent) {
inputId = t.getInputId();
if (n.isFunction() ||
n.isScript()) {
Preconditions.checkNotNull(inputId);
sourceName = NodeUtil.getSourceName(n);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
boolean descend = parent == null || !parent.isFunction() ||
n == parent.getFirstChild() || parent == scope.getRootNode();
if (descend) {
// Handle hoisted functions on pre-order traversal, so that they
// get hit before other things in the scope.
if (NodeUtil.isStatementParent(n)) {
for (Node child = n.getFirstChild();
child != null;
child = child.getNext()) {
if (NodeUtil.isHoistedFunctionDeclaration(child)) {
defineFunctionLiteral(child, n);
}
}
}
}
return descend;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> They also may include pre-processor passes needed to do
* error analysis more effectively.
*
* Clients that only want to analyze code (like IDEs) and not emit
* code will only run checks and not optimizations.
*/
abstract protected List<PassFactory> getChecks();
/**
* Gets the optimization passes to run.
*
* Optimization passes revolve around producing smaller and faster code.
* They should always run after checking passes.
*/
abstract protected List<PassFactory> getOptimizations();
/**
* Gets a graph of the passes run. For debugging.
*/
GraphvizGraph getPassGraph() {
LinkedDirectedGraph<String, String> graph =
LinkedDirectedGraph.createWithoutAnnotations();
Iterable<PassFactory> allPasses =
Iterables.concat(getChecks(), getOptimizations());
String lastPass = null;
String loopStart = null;
for (PassFactory pass : allPasses) {
String passName = pass.getName();
int i = 1;
while (graph.hasNode(passName)) {
passName = pass.getName() + (i++);
}
graph.createNode(passName);
if (loopStart == null && !pass.isOneTimePass()) {
loopStart = passName;
} else if (loopStart != null && pass.isOneTimePass()) {
graph.connect(lastPass, "loop", loopStart);
loopStart = null;
}
if (lastPass != null) {
graph.connect(lastPass, "", passName);
}
lastPass = passName;
}
return graph;
}
/**
* Create a type inference pass.
*/
final TypeInferencePass makeTypeInference(AbstractCompiler compiler) {
return new TypeInferencePass(
compiler, compiler.getReverseAbstractInterpreter(),
topScope, typedScopeCreator);
}
final InferJSDocInfo makeInferJsDocInfo(AbstractCompiler compiler) {
return new InferJSDocInfo(compiler);
}
/**
* Create a type-checking pass.
*/
final TypeCheck makeTypeCheck(AbstractCompiler compiler) {
return new TypeCheck(
compiler,
compiler.getReverseAbstractInterpreter(),
compiler.getTypeRegistry(),
topScope,
typedScopeCreator,
options.reportMissingOverride,
options.reportUnknownTypes)
.reportMissingProperties(options.enables(
DiagnosticGroup.forType(TypeCheck.INEXISTENT_PROPERTY)));
}
/**
* Insert the given pass factory before the factory of the given name.
*/
final static void addPassFactoryBefore(
List<PassFactory> factoryList, PassFactory factory, String passName) {
factoryList.add(
findPassIndexByName(factoryList, passName), factory);
}
/**
* Find a pass factory with the same name as the given one, and replace it.
*/
final static void replacePassFactory(
List<PassFactory> factoryList, PassFactory factory) {
factoryList.set(
findPassIndexByName(factoryList, factory.getName()), factory);
}
/**
* Throws an exception if no pass with the given name exists.
*/
private static int findPassIndexByName(
List<PassFactory> factoryList, String name) {
for (int i = 0; i < factoryList.size(); i++) {
if (factoryList.get(i).getName().equals(name)) {
return i;
}
}
throw new IllegalArgumentException(
"No
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> factory named '" + name + "' in the factory list");
}
/**
* Find the first pass provider that does not have a delegate.
*/
final PassConfig getBasePassConfig() {
PassConfig current = this;
while (current instanceof PassConfigDelegate) {
current = ((PassConfigDelegate) current).delegate;
}
return current;
}
/**
* Get intermediate state for a running pass config, so it can
* be paused and started again later.
*/
protected abstract State getIntermediateState();
/**
* Set the intermediate state for a pass config, to restart
* a compilation process that had been previously paused.
*/
protected abstract void setIntermediateState(State state);
/**
* An implementation of PassConfig that just proxies all its method calls
* into an inner class.
*/
static class PassConfigDelegate extends PassConfig {
private final PassConfig delegate;
PassConfigDelegate(PassConfig delegate) {
super(delegate.options);
this.delegate = delegate;
}
@Override protected List<PassFactory> getChecks() {
return delegate.getChecks();
}
@Override protected List<PassFactory> getOptimizations() {
return delegate.getOptimizations();
}
@Override MemoizedScopeCreator getTypedScopeCreator() {
return delegate.getTypedScopeCreator();
}
@Override Scope getTopScope() {
return delegate.getTopScope();
}
@Override protected State getIntermediateState() {
return delegate.getIntermediateState();
}
@Override protected void setIntermediateState(State state) {
delegate.setIntermediateState(state);
}
}
/**
* Intermediate state for a running pass configuration.
*/
public static class State implements Serializable {
private static final long serialVersionUID = 1L;
final Map<String, Integer> cssNames;
final Set<String> exportedNames;
final CrossModuleMethodMotion.IdGenerator crossModuleIdGenerator;
final VariableMap variableMap;
final VariableMap propertyMap;
final VariableMap anonymousFunctionNameMap;
final VariableMap stringMap;
final FunctionNames functionNames;
final String idGeneratorMap;
public State(Map<String, Integer> cssNames, Set<String> exportedNames,
CrossModuleMethodMotion.IdGenerator crossModuleIdGenerator,
VariableMap variableMap, VariableMap propertyMap,
VariableMap anonymousFunctionNameMap,
VariableMap stringMap, FunctionNames functionNames,
String idGeneratorMap) {
this.cssNames = cssNames;
this.exportedNames = exportedNames;
this.crossModuleIdGenerator = crossModuleIdGenerator;
this.variableMap = variableMap;
this.propertyMap = propertyMap;
this.anonymousFunctionNameMap = anonymousFunctionNameMap;
this.stringMap = stringMap;
this.idGeneratorMap = idGeneratorMap;
this.functionNames = functionNames;
}
}
}
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.CheckLevel;
import java.io.Serializable;
/**
* Class that allows to flexibly manage what to do with a reported
* warning/error.
*
* Guard has several choices:
* - return OFF - suppress the warning/error
* - return WARNING
* - return ERROR report it with high severity
* - return null. Does not know what to do with it. Lets the other guard
* decide what to do with it.
*
* Although the interface is very simple, it allows you easily customize what
* warnings you are interested in.
*
* For example there are could be several implementations:
* StrictGuard - {return ERROR}. All warnings should be treat as errors.
* SilentGuard - {if (WARNING) return OFF}. Suppress all warnings but still
* fail if JS has errors.
* WhitelistGuard (if !whitelistErrors.contains(error) return ERROR) return
* error if it does not present in the whitelist.
*
* @author anatol@google.com (Anatol Pomazau)
*/
public abstract class WarningsGuard implements Serializable {
public static enum Priority {
MAX(1),
MIN(100),
STRICT(100),
DEFAULT(50),
SUPPRESS_BY_WHITELIST(40),
SUPPRESS_DOC(20),
FILTER_BY_PATH(1);
final int value;
Priority(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
/**
* Returns a new check level for a given error. OFF - suppress it, ERROR -
* report as error. null means that this guard does not know what to do
* with the error. Null is extremely helpful when you have a chain of
* guards. If current guard returns null, then the next in the chain should
* process it.
*
* @param error a reported error.
* @return what level given error should have.
*/
public abstract CheckLevel level(JSError error);
/**
* The priority in which warnings guards are applied. Lower means the
* guard will be applied sooner. Expressed on a scale of 1 to 100.
*/
protected int getPriority() {
return Priority.DEFAULT.value;
}
/**
* Returns whether all warnings in the given diagnostic group will be
* filtered out. Used to determine which passes to skip.
*
* @param group A group of DiagnosticTypes.
* @return Whether all warnings of these types are disabled by this guard.
*/
protected boolean disables(DiagnosticGroup group) {
return false;
}
/**
* Returns whether any
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> that there are cycles in the native types
* graph, so some prototypes must temporarily be {@code null} during
* the construction of the graph.
*
* If non-null, the type must be a PrototypeObjectType.
*/
private Property prototypeSlot;
/**
* Whether a function is a constructor, an interface, or just an ordinary
* function.
*/
private final Kind kind;
/**
* The type of {@code this} in the scope of this function.
*/
private ObjectType typeOfThis;
/**
* The function node which this type represents. It may be {@code null}.
*/
private Node source;
/**
* The interfaces directly implemented by this function (for constructors)
* It is only relevant for constructors. May not be {@code null}.
*/
private List<ObjectType> implementedInterfaces = ImmutableList.of();
/**
* The interfaces directly extended by this function (for interfaces)
* It is only relevant for constructors. May not be {@code null}.
*/
private List<ObjectType> extendedInterfaces = ImmutableList.of();
/**
* The types which are subtypes of this function. It is only relevant for
* constructors and may be {@code null}.
*/
private List<FunctionType> subTypes;
/**
* The template type name. May be {@code null}.
*/
private final ImmutableList<String> templateTypeNames;
/** Creates an instance for a function that might be a constructor. */
FunctionType(JSTypeRegistry registry, String name, Node source,
ArrowType arrowType, ObjectType typeOfThis,
ImmutableList<String> templateTypeNames,
boolean isConstructor, boolean nativeType) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
nativeType);
setPrettyPrint(true);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkNotNull(arrowType);
this.source = source;
this.kind = isConstructor ? Kind.CONSTRUCTOR : Kind.ORDINARY;
if (isConstructor) {
this.typeOfThis = typeOfThis != null ?
typeOfThis : new InstanceObjectType(registry, this, nativeType);
} else {
this.typeOfThis = typeOfThis != null ?
typeOfThis :
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
}
this.call = arrowType;
this.templateTypeNames = templateTypeNames != null
? templateTypeNames : ImmutableList.<String>of();
}
/** Creates an instance for a function that is an interface. */
private FunctionType(JSTypeRegistry registry, String name, Node source) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE));
setPrettyPrint(true);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkArgument(name != null);
this.source = source;
this.call = new ArrowType(registry, new Node(Token.PARAM_LIST), null);
this.kind = Kind.INTERFACE;
this.typeOfThis = new InstanceObjectType(registry, this);
this.templateTypeNames = ImmutableList.of();
}
/** Creates an instance for a function that is an interface. */
static FunctionType forInterface(
JSTypeRegistry registry, String name, Node source) {
return new FunctionType(registry, name, source);
}
@Override
public boolean isInstanceType() {
// The universal constructor is its
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> (interfaceType.getConstructor() != null) {
interfaceType.getConstructor().addSubType(this);
}
}
}
}
if (replacedPrototype) {
clearCachedValues();
}
return true;
}
/**
* Returns all interfaces implemented by a class or its superclass and any
* superclasses for any of those interfaces. If this is called before all
* types are resolved, it may return an incomplete set.
*/
public Iterable<ObjectType> getAllImplementedInterfaces() {
// Store them in a linked hash set, so that the compile job is
// deterministic.
Set<ObjectType> interfaces = Sets.newLinkedHashSet();
for (ObjectType type : getImplementedInterfaces()) {
addRelatedInterfaces(type, interfaces);
}
return interfaces;
}
private void addRelatedInterfaces(ObjectType instance, Set<ObjectType> set) {
FunctionType constructor = instance.getConstructor();
if (constructor != null) {
if (!constructor.isInterface()) {
return;
}
set.add(instance);
for (ObjectType interfaceType : instance.getCtorExtendedInterfaces()) {
addRelatedInterfaces(interfaceType, set);
}
}
}
/** Returns interfaces implemented directly by a class or its superclass. */
public Iterable<ObjectType> getImplementedInterfaces() {
FunctionType superCtor = isConstructor() ?
getSuperClassConstructor() : null;
if (superCtor == null) {
return implementedInterfaces;
} else {
return Iterables.concat(
implementedInterfaces, superCtor.getImplementedInterfaces());
}
}
/** Returns interfaces directly implemented by the class. */
public Iterable<ObjectType> getOwnImplementedInterfaces() {
return implementedInterfaces;
}
public void setImplementedInterfaces(List<ObjectType> implementedInterfaces) {
// Records this type for each implemented interface.
for (ObjectType type : implementedInterfaces) {
registry.registerTypeImplementingInterface(this, type);
}
this.implementedInterfaces = ImmutableList.copyOf(implementedInterfaces);
}
/**
* Returns all extended interfaces declared by an interfaces or its super-
* interfaces. If this is called before all types are resolved, it may return
* an incomplete set.
*/
public Iterable<ObjectType> getAllExtendedInterfaces() {
// Store them in a linked hash set, so that the compile job is
// deterministic.
Set<ObjectType> extendedInterfaces = Sets.newLinkedHashSet();
for (ObjectType interfaceType : getExtendedInterfaces()) {
addRelatedExtendedInterfaces(interfaceType, extendedInterfaces);
}
return extendedInterfaces;
}
private void addRelatedExtendedInterfaces(ObjectType instance,
Set<ObjectType> set) {
FunctionType constructor = instance.getConstructor();
if (constructor != null) {
set.add(instance);
for (ObjectType interfaceType : constructor.getExtendedInterfaces()) {
addRelatedExtendedInterfaces(interfaceType, set);
}
}
}
/** Returns interfaces directly extended by an interface */
public Iterable<ObjectType> getExtendedInterfaces() {
return extendedInterfaces;
}
/** Returns the number of interfaces directly extended by an interface */
public int getExtendedInterfacesCount() {
return extendedInterfaces.size();
}
public void setExtendedInterfaces(List<ObjectType> extendedInterfaces)
throws UnsupportedOperationException {
if (isInterface()) {
this.extendedInterfaces = ImmutableList.copyOf(extendedInterfaces);
} else {
throw new UnsupportedOperationException();
}
}
@Override
public JSType getPropertyType(String name) {
if (!hasOwnProperty(name)) {
// Define the "call", "apply", and "
Closure, 12
<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB>
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
<CHANGES>
<CHANGEE>
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
<FILEE>
<SCANS> {
return visitor.caseFunctionType(this);
}
/**
* Gets the type of instance of this function.
* @throws IllegalStateException if this function is not a constructor
* (see {@link #isConstructor()}).
*/
public ObjectType getInstanceType() {
Preconditions.checkState(hasInstanceType());
return typeOfThis;
}
/**
* Sets the instance type. This should only be used for special
* native types.
*/
void setInstanceType(ObjectType instanceType) {
typeOfThis = instanceType;
}
/**
* Returns whether this function type has an instance type.
*/
public boolean hasInstanceType() {
return isConstructor() || isInterface();
}
/**
* Gets the type of {@code this} in this function.
*/
@Override
public ObjectType getTypeOfThis() {
return typeOfThis.isNoObjectType() ?
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE) : typeOfThis;
}
/**
* Gets the source node or null if this is an unknown function.
*/
public Node getSource() {
return source;
}
/**
* Sets the source node.
*/
public void setSource(Node source) {
if (prototypeSlot != null) {
// NOTE(bashir): On one hand when source is null we want to drop any
// references to old nodes retained in prototypeSlot. On the other hand
// we cannot simply drop prototypeSlot, so we retain all information
// except the propertyNode for which we use an approximation! These
// details mostly matter in hot-swap passes.
if (source == null || prototypeSlot.getNode() == null) {
prototypeSlot = new Property(prototypeSlot.getName(),
prototypeSlot.getType(), prototypeSlot.isTypeInferred(), source);
}
}
this.source = source;
}
/** Adds a type to the list of subtypes for this type. */
private void addSubType(FunctionType subType) {
if (subTypes == null) {
subTypes = Lists.newArrayList();
}
subTypes.add(subType);
}
@Override
public void clearCachedValues() {
super.clearCachedValues();
if (subTypes != null) {
for (FunctionType subType : subTypes) {
subType.clearCachedValues();
}
}
if (!isNativeObjectType()) {
if (hasInstanceType()) {
getInstanceType().clearCachedValues();
}
if (prototypeSlot != null) {
((ObjectType) prototypeSlot.getType()).clearCachedValues();
}
}
}
/**
* Returns a list of types that are subtypes of this type. This is only valid
* for constructor functions, and may be null. This allows a downward
* traversal of the subtype graph.
*/
public List<FunctionType> getSubTypes() {
return subTypes;
}
@Override
public boolean hasCachedValues() {
return prototypeSlot != null || super.hasCachedValues();
}
/**
* Gets the template type name.
*/
public ImmutableList<String> getTemplateTypeNames() {
return templateTypeNames;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this);
call = (ArrowType) safeResolve(call, t, scope);
if (prototypeSlot != null) {
prototypeSlot.setType(
safeResolve(prototypeSlot.getType(), t, scope));
}
// Warning about typeOfThis if it doesn't